最近看大佬们闲聊说起页面优化,都觉得后端渲染效果立竿见影,就来学习一波NuxtJS。
<!--more-->
新建Nuxt项目
可以直接使用官方提供的[create-nuxt-app](https://github.com/nuxt/create-nuxt-app)
来进行项目创建:
npx create-nuxt-app <项目名>
经过一些配置项的选择之后等待完成安装即可
另外也可以手动创建项目,创建好package.json
:
{
"name": "my-app",
"scripts": {
"dev": "nuxt"
}
}
然后运行:
npm install --save nuxt
然后即可创建pages
文件夹,创建一个pages/index.vue
,启动项目npm run dev
即可在http://localhost:3000
访问到
目录结构
目录结构都是一些约定大于配置的东西,只有一些注意项:
components
文件夹下的组件不会像页面组件那样有asyncData
方法的特性nuxt.config.js
文件用于组织Nuxt.js
应用的个性化配置,以便覆盖默认配置
另外还有约定的别名:
~
或@
srcDir
~~
或@@``rootDir
默认情况下,srcDir
和 rootDir
相同。
路由
一直在强调,NuxtJS
是约定大于配置的,在pages
文件夹下创建的组件都会被视为页面组件,根据嵌套关系和文件命名会自动生成路由配置文件
假设现有:
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
自动生成的路由配置如下:
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'user',
path: '/user',
component: 'pages/user/index.vue'
},
{
name: 'user-one',
path: '/user/one',
component: 'pages/user/one.vue'
}
]
}
当有以下划线作为前缀的
Vue
文件时,会被视为动态路由,如:_id.vue
另外一提,Nuxt
提供了一个新的路由跳转组件<nuxt-link>
, 比起Vue
的<router-link>
新增了一些功能,建议使用<nuxt-link>
来看一个动态路由和路由参数校验的例子:
假设现在在Pages/About/
下有一个_id.vue
组件为:
<template>
<div class="id">
dynamic router params.id: {{id}}
</div>
</template>
<script>
export default {
name: "dynamicId",
asyncData({params}) {
return {
id: params.id // 在asyncData里面取到id作为本组件的data
}
},
validate ({ params }) { // Nuxt.js提供了validate()方法可以让你在动态路由组件中定义参数校验方法。
// 假设id不能大于100
return Number(params.id) <= 100;
}
}
</script>
当我们访问http://localhost:3000/about/100
时一切良好,当访问http://localhost:3000/about/200
时提示页面找不到。
后续还有嵌套路由和动态嵌套路由,基本原理跟Vue
是一模一样的
自动生成的router文件可以查看
.nuxt/router.js
路由的全局守卫
关于全局守卫,完全可以当做一个插件来进行引用,我们可以建立:/plugins/router.js
:
export default ({app}) => {
app.router.beforeEach((to,form,next) => {
console.log(to)
next();
});
}
然后在nuxt.config.js
中增加配置:
plugins: [
'@/plugins/element-ui',
'@/plugins/router' // 增加全局守卫插件
],
layouts
默认使用的是layouts/default.vue
,可以理解为这个相当于Vue
中的App.vue
,我们可以通过修改这个组件来添加一些全局的东西。
另外也可以新增新的布局文件,比如我们可以新增一个layouts/about.vue
:
layouts/about.vue
:
<template>
<div>
<h1>This is About Page</h1>
<nuxt />
</div>
</template>
<script>
export default {
name: "about"
}
</script>
然后在pages/About/index.vue
中指定布局即可
<template>
<div class="aboutIndex">
pages/About/index.vue
</div>
</template>
<script>
export default {
layout: "about" // 指定使用的布局
}
</script>
定义了布局之后,任何指定该布局的页面都会使用该布局
当然也可以自定义404页面:
我们可以创建一个特殊的layouts/error.vue
,虽然此文件放在 layouts 文件夹中, 但应该将它看作是一个 页面(page),且error.vue
不需要包含<nuxt/>
标签。
异步数据
asyncData()
asyncData()
方法可以在设置组件的数据之前能一步获取或者处理数据,最关键的是其支持异步数据.
等待异步结果返回之后,Nuxt.js
会将asyncData
返回的数据融合组件data
方法返回的数据一并返回给当前组件。
asyncData
方法支持多种异步写法,返回一个Promise
或者async/await
都是可以的
返回一个Promise
:
<script>
let p = new Promise((resolve) => {
setTimeout(() => {
resolve("DeeJay");
}, 3000)
});
export default {
asyncData ({params}) {
return p.then(res => {
return {
name: res,
id: params.id
}
})
},
}
</script>
async/await
写法:
<script>
let p = new Promise((resolve) => {
setTimeout(() => {
resolve("DeeJay");
}, 3000)
});
export default {
async asyncData ({params}) {
let res = await p;
return {
name: res,
打开新页
id: params.id
}
},
}
</script>
此外asyncData
还提供了callback
写法,对于第二个参数callback
,我们可以打印出来为:
// 内部的callback实现,所以我们第一个参数如果为null则说明成功,如果不为null则说明在做错误处理
function (err, data) {
if (err) {
context.error(err);
}
data = data || {};
resolve(data);
}
具体的用法就是在Promise
写法或者async/await
写法中不进行return
,直接调用callback
即可:
<script>
let p = new Promise((resolve) => {
setTimeout(() => {
resolve("DeeJay");
}, 3000)
});
export default {
async asyncData ({params}, cb) {
let res = await p;
cb(null, {
name: res,
id: params.id
});
// return {
// name: res,
// id: params.id
// }
},
}
</script>
这种写法多用于错误处理的情况
asyncData中的第一个参数,即上下文对象
上文提到的asyncData中的第一个参数,即为上下文对象,其内部详细的属性见这里
我们一般都只通过解构获取想要获得的对象,比如req
res
params
以及error
等
错误处理
错误处理有2种方法:
上文提到的上下文对象中的
error
即为我们作为错误处理的方法使用
callback
使用例子:
context.error
:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 3000)
});
export default {
async asyncData ({params, error}) {
return p.then(res => {
return {
name: res,
id: params.id
}
}).catch(() => {
error({ statusCode: 404, message: 'Error msg!' })
})
},
}
</script>
callback
:
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 3000)
});
export default {
async asyncData ({params}, callback) {
return p.then(res => {
return {
name: res,
id: params.id
}
}).catch(() => {
callback({ statusCode: 404, message: 'Error msg!' })
})
},
}
</script>
插件
对于第三方的插件,直接npm install
之后在组件内部引用使用即可
但是对于Vue
的插件,需要在/plugins/
下建立一个js
文件,比如我们要引入element-ui
先建立plugins/element-ui.js
:
// plugins/element-ui.js
import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
Vue.use(Element, { locale })
然后还需要在nuxt.config.js
下进行配置:
// nuxt.config.js
export default {
// balabala 其他配置
plugins: [
'@/plugins/element-ui',
],
}
Nuxt中使用Vuex
依旧是约定大于配置:Nuxt
会自动找到/store
目录,进行引用Vuex
等工作
在此只介绍Nuxt官方推荐使用方式
/store
目录下的每个.js
文件会被转换成为状态树指定命名的子模块 (当然,index
是根模块)
state
的值应该始终是function
,为了避免返回引用类型,会导致多个实例相互影响。
来看具体的使用例子:
// /store/index.js
export const state = () => ({
counter: 0
});
export const mutations = {
increment(state) {
打开新页
state.counter++;
}
};
再创建一个子模块/store/otherModule.js
// /store/otherModule.js
export const state = () => ({
otherCounter: 0
});
export const mutations = {
add(state, number) {
state.otherCounter += number;
}
};
此时nuxt
自动生成的Store
为:
new Vuex.Store({
state: () => ({
counter: 0
}),
mutations: {
increment (state) {
state.counter++
}
},
modules: {
otherModule: {
namespaced: true,
state: () => ({
otherCounter: 0
}),
mutations: {
add(state, number) {
state.otherCounter += number;
}
}
}
}
})
在组件内部我们就可以进行使用了:
<template>
<div class="id">
<div>dynamic router params.id: {{id}}</div>
<div>counter: {{counter}}</div>
<div>otherCounter: {{otherCounter}}</div>
<el-button @click="addCounter">click to plus one to counter</el-button>
<el-button @click="addOtherCounter">click to plus one to otherCounter</el-button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
async asyncData ({params}) {
return {
id: params.id
}
},
computed: {
counter() {
return this.$store.state.counter
},
otherCounter() {
mutations: {
add(state, number) {
state.otherCounter += number;
}
}
}
}
})
在组件内部我们就可以进行使用了:
<template>
<div class="id">
<div>dynamic router params.id: {{id}}</div>
<div>counter: {{counter}}</div>
<div>otherCounter: {{otherCounter}}</div>
<el-button @click="addCounter">click to plus one to counter</el-button>
<el-button @click="addOtherCounter">click to plus one to otherCounter</el-button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
async asyncData ({params}) {
return {
id: params.id
}
},
computed: {
counter() {
return this.$store.state.counter
},
otherCounter() {
return this.$store.state.otherModule.otherCounter
}
},
methods: {
...mapMutations({
increment: "increment",
add: 'otherModule/add',
}),
addCounter() {
this.increment();
},
addOtherCounter() {
this.add(1);
}
}
}
</script>
Nuxt支持TypeScript
笔者在这里踩过坑,所以注明以下配置都是针对
Nuxt 2.10
及以上的版本进行切换到TS,对于低版本的,安装@nuxt/typescript
和ts-node
即可
对于Nuxt 2.10
及以上的版本来说,想要支持TypeScript
需要安装@nuxt/types
@nuxt/typescript-build
以及@nuxt/typescript-runtime
其中@nuxt/typescript-build
和@nuxt/typescript-runtime
都已经集成了@nuxt/types
,没必要进行单独安装,另外@nuxt/typescript-runtime
是可选安装的。
@nuxt/typescript-build
如果只想对于layouts
,components
,plugins
以及middlewares
这几个文件夹下的做ts支持的话,我们只需要安装@nuxt/typescript-build
即可
npm install --save-dev @nuxt/typescript-build
然后修改nuxt.config.js
:
// nuxt.config.js
export default {
buildModules: ['@nuxt/typescript-build']
}
最后创建一个tsconfig.json
:
// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"experimentalDecorators": true,
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/types"
]
},
"exclude": [
"node_modules"
]
}
这边要注明一点,如果最后是想要通过
class API
的风格进行编写组件的话,需要引入vue-property-decorator
,所以在tsconfig.json
中需要加上"experimentalDecorators": true,
这个选项,否则会有报错提示。
@nuxt/typescript-runtime
@nuxt/typescript-runtime
针对的是那些不会被webpack
编译的文件,比如说nuxt.config
,还有本地的模块以及serverMiddlewares
等。
其内部使用了ts-node
进行编译这些文件。
npm install @nuxt/typescript-runtime
安装完成后需要修改npm scripts
:
"scripts": {
"dev": "nuxt-ts",
"build": "nuxt-ts build",
"generate": "nuxt-ts generate",
"start": "nuxt-ts start"
},
然后就可以进行开发了,对于组件内部的写法,有传统的optional API
写法,也有Vue
新出的composition API
写法,以及使用vue-property-decorator
的Class API
具体可以见官方的cookbook
笔者是习惯写class API
的,放一个实例上来:
<template>
<div id="App">
<div>{{msg}}</div>
<div>{{msg2}}</div>
</div>
</template>
<script lang="ts">
import {Vue, Component} from "vue-property-decorator";
import { Context } from "@nuxt/types";
let p = new Promise<string>((resolve => {
setTimeout(() => {
resolve("Hi, this is msg from Promise");
}, 3000);
}));
@Component({
asyncData(ctx: Context) {
return p.then(res => {
return {
msg2: res
}
})
}
})
export default class App extends Vue {
msg: string = "msg from data";
}
</script>
其余SSR替代方案
由于已有Vue
项目迁移到NuxtJS
项目需要较大工作量的重构(二者的规则约定差别较大)
所以如果需要进行SSR的页面较少、页面内容实时性要求较低的话,可以考虑:
另附上相关阅读: