一.了解nuxt框架:
1)从背景出发
随着各大领域与行业互联网的普及,各大行业都在争取,自家品牌或产品的曝光率多一些,SEO已经成为前端领域必须考虑的一个严谨的问题。
而SPA架构的普及,SPA的弊端也逐步凸显出来。众所周知,SPA(单页面)架构的弊端,不利于搜索引擎优化,还有首屏慢等问题。
此时,各大框架,都在考虑自己的"服务端渲染"方案。于是,在react推出next之后,vue也推出了nuxt。
笔者第一次看到"nuxt"这个专业名称,是来自vue官网"服务端渲染"指南:
那么nuxt是什么?看看百度搜索引擎如何介绍nuxt的:
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
简单的来说,就是基于原有的通用应用框架(如vue或react), 通过对客户端/服务端基础架构的抽象组织,进行应用的UI渲染。
2)从官网出发
我们再来从nuxt官网继续了解:
好吧,英文有点一脸懵逼,翻译一下:
中文版总算,能略能看懂一二,描述一下个人的理解:
* 1)Nuxt自带了modules系列。
如何理解这里的modules?文章下边会有介绍。这里跟常规前端理解有些不一致。常规的前端,只需要考虑客户端。而nuxt毕竟是一个服务端框架,所以有一些方法必须考虑客户端,以及服务端。如网络请求,如样式生成。所以,modules的出现,就是解决两个客户端的共性。
目前官方提供的modules提供100+模块,我们也可以自定义modules进行开发。
复制代码
* 2)高性能
单从简单的文字的介绍,就是利用 Vue.js 和 Node.js两者优势构成好更高效的性能。
* 3)愉快的
为什么nuxt是enjoyable呢?个人的观点,是vue的开发者切换nuxt,因绝大多数的开发与vue-cli雷同,我们只需学习好两者差异的地方。无需太消耗太多的精力去重新学习。了解一些nuxt的特性,开箱即用,即可进行"愉快的"开发。
3)从源码出发
此时,nuxt大概是什么我们应该略有了解。但具体由什么组成呢?
我们再继续从源码的角度,继续往下理解nuxt:
源码分析,能力有限,如有描述不对望指出
首先,由node_modules/nuxt/目录,可以简单看出,nuxt的主体,脚手架指向@nuxt/cli,而打包过程,指向@nuxt/builder:
再继续往下看由node_modules/@nuxt,我们可以查看到大概目录组成:
根据目录我们就可以简单猜测:
- 整个协同过程在cli目录
- webpack跟core等目录,可以猜测到大概编译过程。
- nuxt的打包代码生成过程,主要在cli,builder,generator等目录。
- 还搭配一些公用的方法,如components,utils等。
- 前端模块主要在vue-app。
- 服务端模块主要在server。
我们根据上边@nuxt/cli的指引,可以来验证一下猜测的服务端跟客户端的的是否正确。
查看一下@nuxt/builder的代码:
整个打包的过程如上代码,其中获取vue代码的文本,笔者定位在template中,即来自vueApp。而const vueApp = require('@nuxt/vue-app');。我们可以看出,客户端的代码主要来自vue-app。我们再继续看看vue-app是什么。一起看看vue-app的源码:
根据引入的package.json,我们可以明白,客户端用的就是我们常用的vue + vue-router + vuex + vue-template-compiler + 其他工具包"。这基本,就是跟我们的vue-cli 3.0大同小异。当然,中间路由等,nuxt处理了他的二次封装,包括目录方法等调整。这也证明了,为什么绝大多数的代码跟vue-cli 3.0相同。
继续再来跟踪一下服务端(@nuxt/server):
再结合:
const connect = require('connect');
const connect__default = /*#__PURE__*/_interopDefaultLegacy(connect);
复制代码
由此,我们可以跟踪到,Nuxt的服务端,使用的是connect框架(貌似还有nuxt-koa版本),再结合中间的nuxtMiddleware,renderAndGetWindow等,构造成我们的nuxt的服务端。
再继续看看是如何渲染到页面的,即服务端是如何渲染成html给客户端的呢:
由上方截图可分析:
this.renderer = new vueRenderer.VueRenderer(this.serverContext);
它是在connect生命周期ready还没结束时,采用vueRenderer渲染到对应的实例中。那么vueRenderer又是啥?我们引入继续往下:
const vueRenderer = require('@nuxt/vue-renderer');
由此,我们可以分析出,实际上的nuxt服务端渲染,就是利用vue-server-renderer包,渲染到对应的实例。
vue-server-renderer,相信部分小伙伴已经接触过。如果要用vue自己写ssr的基本都会使用到。
可以看看官网的介绍:ssr.vuejs.org/guide/struc…
这里官网的图例可以帮助我们很好的理解:
vue-server-renderer将识别我们原本spa架构的app.js,根据生命周期等区分好对应的内容属于客户端(client)还是服务端(server),通过webpack,分别生成server.bundle.js与client.bundkle.js。
此时,如果浏览器访问,先进入node层,执行完server.bundle.js,然后生成对应的html输出到浏览器。此时再继续执行client.bundle.js,执行对应的客户端代码。
至此,我们可以了解到,nuxt的客户端组成,服务端组成,以及服务端是如何渲染给客户端的。
二. nuxt框架分析
了解完上面是nuxt,我们继续分析一下nuxt的特性。
本章节主要与vue-cli 3.0作为参照物作为对比。
1)menu / 目录结构
目录结构,与vue-cli 3.0大同小异,其最大的区别,就是一个在根目录中,另一个在src的二级目录中。
因为nuxt需要考虑两个客户端,目录结构更类似koa或express等服务端框架。
下边可观察两个新建项目的图例:
这里简单列举一下常用的异同:
名称 | vue-cli | nuxt |
---|---|---|
页面 | src/views | pages |
组件 | src/components | components |
vuex | src/store | store |
静态资源 | src/static | static |
公用方法 | src/${js} | plugins |
路由 | src/${自定义} | / |
布局 | / | layout |
中间层 | / | middleware |
配置文件 | vue.config.js | nuxt.config.js |
2)context / 上下文
看看官网如何介绍的context:
将
context
提供额外的对象/ PARAMS从Nuxt到Vue公司的组件和在特殊nuxt生命周期的区域提供像asyncData
,fetch
,plugins
,middleware
和nuxtServerInit
。
那么这个额外的对象,附带的什么信息?作用是什么?
个人通俗一点的理解:nuxt因需要同时考虑服务端与客户端,原本前端的嵌入到js原型实例的方法已经不适用,服务端需要考虑与客户端考虑数据传输的问题。而nuxt提供的context对象,快速帮助服务端获取对应的数据,如app,store,router, env等,达到两个客户端数据相通的目的。
具体提供的所有对象,可参考上述链接。包括app, store, route, params, query, env, isDev, isHMR等。
store, route, params, query等,跟vue实例差异不大,只是nuxt提前帮我们注入到服务端实例中,我们这里解释一下app:
包含所有插件的根 Vue 实例选项。例如,在使用的时候i18n
,你可以访问$i18n
通过context.app.i18n
。
个人理解:同等于客户端的this
。在spa页面的获取Vue实例,只需要通过this对象即可获取。而在服务端,需要借助nuxt提供的app才可以获取到对应的实例。
这里的app,并不等同于页面的this。在特定的生命周期声明,才可以获取得到。如果需要提前注入, 那还要借助plugins机制提前注入。
3)layout / 布局
layout可能对于一个刚接触nuxt的有点懵逼,毕竟vue-cli没有。
其实非常好理解。vue-cli支持子父路由嵌套,而nuxt的layout,就可以简单理解是vue-cli最外层的嵌套。由于nuxt的路由,是自动生成的。
配置子父路由相对复杂,所以nuxt引入了layout,只需要在对应的页面配置layout:layoutName,就能完成快速的嵌套。非常方便。
借助来自官网的视图布局图片,可以看到,HTML页面作为最外层,接下来是Layout布局,再嵌入我们的Page页面。
提到HTML,NUXT还支持外层HTML的布局编辑,可参考: www.nuxtjs.cn/guide/views…
4)router / 路由
vue-cli与nuxt的路由,无论是原理还是实践,差异还挺大的。毕竟一个是服务端路由,一个是纯前端路由。
router区别1:名称不同
这里只是名称叫法不一致,用法都雷同,不再描述。
1) <router-link /> 切换成 <nuxt-link />
2) <router-view /> 切换成 <nuxt />
复制代码
router区别2: 自动生成
首先,nuxt的路由是可以自动生成的,这点跟传统的vue-cli有些不一致。
当然,你也可以利用nuxt.config.js的进行自定义:
router: {
extendRoutes(routes, resolve) {
routes.push({
name: 'test-jia',
path: '/test/jia',
component: path.resolve(__dirname, '../pages/test/jia.vue'),
})
}
}
复制代码
当然如果你还想全局接管也支持:
router: {
extendRoutes(routes, resolve) {
return [{
name: 'test-jia',
path: '/test/jia',
component: path.resolve(__dirname, '../pages/test/jia.vue'),
}]
}
}
复制代码
router区别3:路由钩子
nuxt的路由,相比vue-cli复杂一些。nuxt需要借助plugin或者middleware才能完成对路由的全局守卫。可参考:
plugins: [
{ src: '@/plugins/router.js', ssr: false}
],
export default ({ app, store, $axios }) => {
app.router.beforeEach((to, from, next) => {
next()
})
}
复制代码
router区别4:原理性质不同
一个为"前端路由",另外一个为"后端路由",两者的原理是不同的。可以搜索"前端路由"与"后端路由"的差异。
这里简述一下两者的特性:
后端路由:
个人理解:更像一个页面级程序,每个页面独立存在。
- 1.分担了浏览器的压力,html和数据的拼接都是由服务器完成。
- 2.支持SEO。
- 3.页面较大时候,更容易出现白屏的情况。
- 4.该模式需要借助服务端,且前后端不分离。
- 5.符合浏览器的前进后退机制标准,可有效利用缓存。
前端路由:
个人理解:更像一个应用级程序,加载后可以独立于浏览器运行。
- 1.无服务器压力(除带宽),加载执行全在客户端。
- 2.不支持SEO
- 3.用户体验相对较好
- 4.浏览器的前进后退机制,都会重新执行,减低缓存使用率。
- 5.无法记录滚动位置等。
router区别5:监听方式不同
全局监听:
vue-cli有自带的路由的路由钩子函数,而nuxt笔者并没有找到官方自带的路由钩子函数。需要借助plugins机制来完成全局的钩子。
找到一个成品的案例:www.zhaima.tech/post/nuxt.j…
页面监听:
vue-cli页面监听,直接watch页面的router即可。而nuxt无法通过watch完成。nuxt有自带的watchquery监听路由参数变化。
值得一提的是,watchquery有个大坑需要注意,他需要在该函数return回去,才可以做到事实。其二,如果用到plugin注入时,该watchquery的this对象会改变。
5)axios / 网络请求
此时的网络请求,需要考虑两个客户端,故原来的axios在服务端是需要处理做兼容的。
好在官方提供了@nuxtjs/axios,我们只需在modules配置上就可以使用。
modules: [ "@nuxtjs/axios"]
复制代码
此时,axios可能还面临一个问题,它需要从实例发起请求,但此时你并没有实例。这时候你需要借助plugins:
export default function(context, inject) {
context.$axios.onRequest((config) => {
...
});
}
复制代码
6)plugins / 插件机制
在传统的spa页面,执行顺序,是优先生成了实例,再执行对应的js。此时所有的js,想获取到对应的实例,是十分简单的,直接js,或者import vue form 'vue'引入接口。
但是在ssr中,服务端跟客户端是无法同时使用同一个实例的。毕竟server与client还是存在差异。
如果我们自己写ssr框架的话,这将是一大痛点。而nuxt帮我们处理了这个事情,引入了plugins。让在server的生明周期,也可以获取到对应的实例。
给plugins一个简单的理解的话,就是: 需要在根vue.js应用实例化之前需要运行的JavaScript插件
值得注意的是,在配置 plugins 的时候,如果 ssr:false ,将会在asyncData。
或者mode: 'client' || 'server'控制终端。
7)middleware / 中间层
简单理解:存放应用的中间件
这个其实很多node端的小伙伴的都有这个概念,无论是koa还是express,他们都有自己的middleware机制。middleware即是在进入页面,或者是进入方法之前,优先进入中间层判断(下边生命周期会提及)。如果不满足条件,将用中间的方法控制如何处理。
有点类似我们的"路由钩子函数"。但前端"路由钩子函数"已经进入的页面进行加载,而nuxt,此时还在服务端,会进行中断。
该场景最典型的,鉴权,终端判断,埋点等场景。
此外,nuxt中的middleware可分为,全局middleware与布局middleware,布局匿名middleware,页面middleware,以及页面匿名middleware。
他们的执行顺序是:全局(config) > 布局(layout) > 页面(page)
middleware/auth.js:
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.authenticated) {
return redirect('/login')
}
}
复制代码
- 全局案例:
nuxt.config.js:
module.exports = {
router: { middleware: ['auth'] }
}
复制代码
- 页面实名案例:
export default {
middleware:"auth"
}
复制代码
- 页面匿名案例:
export default {
middleware({ store, redirect }) {
// If the user is not authenticated
if (!store.state.authenticated) {
return redirect('/login')
}
}
}
复制代码
参考文档:www.nuxtjs.cn/guide/routi…
8)modules / 模块
modules在上述官网中,以及成为重点介绍的优势。目前官方是显示内提供100+模块。
何为模块?模块是Nuxt.js扩展,可以扩展其核心功能并添加无限的集成。模块只是在引导Nuxt时按顺序调用的函数。框架在加载之前等待每个模块完成。
如果是plugins的机制,为我们解决编译后的需要的解决方案,那么modules就是解决我们编译前的所需。例如,项目启动前,你要你的项目支持typescript,支持proxy,支持eslint等等。
在过去,你可以指需要考虑客户端如何引入它们。而seo框架中,你需要考虑的是服务端和客户端如何同时引入他们,且兼容,这个过程,是可以十分复杂的。就单单this的实例对应的传递,都足够我们折腾。
而nuxtjs官方的modules的封装好了各个常用的模块,方便开发者使用。我们只需要在开发时配置使用他们即可。正如官网的原话:模块列表 目录包含要在实例化根 Vue.js 应用程序之前运行的 JavaScript 插件。这是添加 Vue 插件和注入函数或常量的地方。每次需要使用时 ,都应该在 in 中创建一个文件 并将其路径添加到 in 以增强您的 Nuxt 项目
可以看看官方提供的典型模块:modules.nuxtjs.org/
当然,nuxtjs的modules模块,不仅可以使用官方的。我们在开发过程中也允许自定义,甚至可以发布到官网的npm中。
教程在:zh.nuxtjs.org/docs/2.x/di…
9)cookie / 储存
前端常见的储存方式,localStorage,sessionStorage,cookies,indexedDB等。 但是在我们nuxtjs中,考虑的不仅仅是前端浏览器。sessionStorage与localStorage等,他的储存位置,正是浏览器。所以,除非当前缓存,确认只有客户端使用,如果一旦服务端使用,程序将无法识别对应的储存,程序将执行异常。
而在框架的实践过程中,很多数据需要服务端与客户端共享,典型的案例就是用户token。cookies貌似是当前最好的解决方案。
-
可以使用cookie-universal-nuxt
npm i --save cookie-universal-nuxt nuxt.config.js { modules: ['cookie-universal-nuxt'] } app.$cookies.get('token') 复制代码
该方案,会将cookies内嵌到我们this或者app对象中。 可参考:blog.csdn.net/weixin_3618…
-
也可以使用js-cookie
npm i --save js-cookie import Cookies from 'js-cookie' Cookies.get('token'); 复制代码
该方案,使用cookies需要手动引入cookie。
10)proxy / 跨域
nuxt的proxy代理,跟vue-cli差不多,在配置文件配置上代理的域名即可。只是proxy需要考虑一下服务端代理。
官方已经帮我们提供的模块@nuxtjs/proxy,我们直接配置即可。
modules: ["@nuxtjs/proxy"]。
proxy: {
'/api/': {
target: 'http://*********/',
pathRewrite: {
'^/api/': ''
}
}
复制代码
11)content / 内容
该模块属于nuxtjs的扩展之一。此部分有接触过vue press的小伙伴们比较熟悉。
如何快速支持,Markdown,CSV、YAML、JSON(5)、XML等标准格式的展示,content帮我们封装好了快速集成。我们只需要快速的引入即可。
首先我们,在nuxt.config.js引入对应的content模块,先安装对应的的模块:
npm install @nuxt/content --save
复制代码
修改nuxt.config.js:
export default {
modules: [ "@nuxt/content"],
}
复制代码
这时候我们就可以使用content的功能,完成对扩展类型的服务端渲染,我们以典型的md文件为案例。
新建文件/content/helloWord.md:
## 这是一个md文件
md文件解析成功
复制代码
此时,我们页面在中即可快速引入:
<nuxt-content :document="doc" />
async asyncData({ $content, params }) {
const doc = await $content('helloWord').fetch();
return {
doc
};
},
复制代码
此时,即可展示标准的md文件展示:
值得一提的是,这里官方还支持获取文件夹遍历文件的功能,同时支持拿文件内部属性的功能,如获取md文件的二级标题等。
再值得一提的是,获取文件,可以通过asyncData与fetch周期。这里区别,在后续的生命周期介绍。
特别注意:实践中发现,开发模式,文件解析后,并不会自动更新。如需要热更新需要特殊处理。
12)life / 生命周期
nuxt的生命周期,比vue spa复杂一下。我们先看看官网的图例(链接:nuxtjs.org/docs/concep…
再看看服务端执行的过程:
再看看客户端执行的过程:
具体的执行过程,相信官方截图已经很清晰。下边挑几个重要的生命周期说明。
13)nuxtServerInit / 初始化store
看看来自官网的解释:
如果
nuxtServerInit
在 store 中定义了 action 并且 mode 为universal
,Nuxt 将使用上下文调用它(仅从服务器端)。当我们想要直接提供给客户端的服务器上有一些数据时,它很有用。
例如,假设我们在服务器端有会话,我们可以通过 访问连接的用户 req.session.user
。要将经过身份验证的用户添加到我们的商店,我们将我们更新 store/index.js
为以下内容.
个人的理解,就是nuxt框架在初始化的过程中,需要从服务端获取数据(同步获取),这时候就可以利用store自带的nuxtServerInit函数进行赋值。由上边生命周期图可以看出,nuxtServerInit是初始化的第一步骤。
如页面加载时,先判断token在服务端是否生效。如页面加载前,实时获取用户信息。等等,此时 nuxtserverinit 将给我们带来大大的用处。
14) created & 与beforeCreated /页面初始化
created与beforeCreated,这两个生命周期貌似熟悉,但却与vue-cli的不同。
细心的小伙伴们在上图中可以看出,该生命周期,即属于客户端,也属于服务端。这里有什么区别呢?
我们看看看实验1:
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: "homeIndex",
data() {
return{
name: "小明",
}
},
created(){
this.name = "小刚";
}
};
</script>
复制代码
页面肯定展示name为"小刚",我们查看渲染结果:
这是返回的html已经是渲染完成的。再看实验2(加了setTimeout):
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: "homeIndex",
data() {
return{
name: "小明",
}
},
created(){
setTimeout(()=>{
this.name = "小刚";
}, 400)
}
};
</script>
复制代码
此时,页面展示结果为小刚,但html渲染结果却为小明。
其他宏任务微任务这里不再试验,我们可以得出结论: created周期的赋值,只有同步代码,可以达到SEO的效果,如果需要用到异步任务,created无法满足渲染的需求。
15)asyncData / 服务端请求
created无法达到异步任务SEO效果。那么如果,需要SEO渲染,如何处理呢?这就是asyncData存在的意义。
来看官网的介绍:
你可能想要在服务器端获取并渲染数据。Nuxt.js添加了asyncData方法使得你能够在渲染组件之前异步获取数据。
由此理解,asyncData差不多是NUXT服务端渲染的关键生命周期。该生命周期属于服务端,即在进入客户端之前,请求好数据展示前拼接好html返回客户端。
因为属于服务端渲染,我们也需特别注意,无法获取到属于客户端的东西,如window,document。其他类似store, this对象等,也需要借助上文说的context完成交互。
这里还有一个大坑,asyncData 只在首屏被执行,其它时候相当于 created 或 mounted 在客户端渲染页面。
也就说,asyncData达到首屏初始化效果的,只能是从浏览器输入url的那个链接,或者重新刷新后的链接。如果是当前页面的跳转,它仅是客户端加载,无法达到渲染的效果。
且asyncData为页面级生命周期,不支持组件级别。
16)fetch / 异步获取store
- 官网的介绍:
fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。
从字面理解,asyncData可以填充页面data的数据,而store就需要fetch函数填充。实际后,其实asyncData也支持store的变化。
特别注意fetch为异步。
特别注意,asyncData为页面级别,而fetch为组件级别
什么意思,我们一起看看下边的测试案例:
新建页面index.vue:
<template>
<div>
<h-child></h-child>
<h-child></h-child>
</div>
</template>
<script>
import hChild from "./components/child.vue";
export default {
components: {
hChild
},
asyncData(){
console.log(`page.asyncData============`);
},
fetch(){
console.log(`page.fetch============`);
},
};
</script>
复制代码
再新建组件/components/child.vue:
<script>
export default {
name: "homeIndex",
data() {
return{
name: "小明",
}
},
asyncData(){
console.log(`child.asyncData============`);
},
fetch(){
console.log(`child.fetch============`);
},
};
</script>
复制代码
此时看一下页面的打印结果:
我们可以得出上述结论,asyncData只在页面生效,而fetch针对所有组件生效。
17)validate / 校验
看看来自官网的解释:
validate
每次导航到新路线之前都会调用。它将在服务器端调用一次(在对 Nuxt 应用程序的第一次请求时)和客户端在导航到更多路由时调用。此方法将context
对象作为参数。
个人的可以简单理解成,路由钩子函数的拦截。我们可以在这里进行校验路由的参数等。
如商品详情链接,一定要有id:
export default {
validate({ query }) {
return query.id;
}
}
复制代码
三. nuxt槽点
这里列举笔者在实际过程中,遇到可以吐槽的几个点。有解决方案的欢迎分享。也欢迎补充槽点。
1)服务端渲染兼容性较差
代码要求率更高。当执行到错误的代码时,传统前端框架,只是抛出异常,无法执行下边的逻辑,页面甚至不受影响。 而服务端框架,如果没有处理好,将出现可怕的500页面等。
2)无法获取浏览器信息
传统的spa项目因为请求都在客户端,是可以实时获取到浏览器信息等。 然而在nuxt中,请求如果来自服务端,此时是没有对应的信息的。
3)调试窗口限制
开发实践得出的结论,nuxt最多只支持3个窗口的调试。当打开第四个页面,将会一直白屏。
4)调试延迟较高
不知道有无小伙伴遇到,同样的代码在nuxt跟vue-cli,响应速度慢了整整一拍?
5)获取content文件不支持热更新
开发过程中,获取上述所说的content时,只有第一次会加载。后续将直接从内存获取,也就是说,文件怎么换,甚至删除,调试都无法生效。
6)页面监听watchQuery
watchquery需要注意,他需要在该函数return回去,才可以做到实时渲染。其二,如果用到plugin注入时,该watchquery的this对象会改变。
7)asyncData只有首屏生效
按正常人的思维,asyncData应该由每个页面触发。而asyncData达到首屏初始化效果的,只能是从浏览器输入url的那个链接,或者重新刷新后的链接。如果是当前页面的跳转,它仅是客户端加载,无法达到渲染的效果。
8)按需引入
类似一些第三方库,如果继续使用全部引入的方式,将会使包的大小大大的增加,此时需要考虑如何优化的问题。按需引入是一个需要注重的问题。
四. nuxt展望
1) nuxt部署
nuxt的部署比纯html部署稍微复杂一丢丢。后续分享pm2部署,以及docker上如何部署nuxt。
2) nuxt重构
看完nuxt,可能有种感觉,vue-cli可以快速切换到nuxt。然而重构的过程中,会发现千疮百孔。一下子很能迁移过来。个人正在尝试,利用qiankun完成过渡以及重构,后续会分享demo,敬请期待。
3) nuxt 3
当撸完nuxt的时候,发现nuxt 3已经出现。有使用vue3的小伙伴们可以移步nuxt3。