nuxt.js相关
Nuxt
简单说,Nuxt
就是基于Vue
的一个应用框架,采用服务端渲染
,让你的SPA应用(Vue)也可以拥有SEO
生命周期
Vue
的生命周期
全都跑在客户端
(浏览器),而Nuxt
的生命周期有些在服务端(Node),客户端,甚至两边都在:
生命周期流程图,红框内的是Nuxt的生命周期(运行在服务端)
,黄框内同时运行在服务端&&客户端上
,绿框内则运行在客户端
创建nuxt项目
使用nuxt官网提供的脚手架
// nuxt-learn 是项目名
npx create-nuxt-app nuxt-learn
兼容es6语法(import)
初始化的项目使用了nodemon监听和热刷新脚本,仅支持require语法,不支持es6的imoort语法
使用nodemon
可以监听文件修改,然后让服务器自行重启。
// package.json
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
"build": "nuxt build",
"start": "cross-env NODE_ENV=production node server/index.js",
"generate": "nuxt generate"
},
// 正常
const Koa = require('koa')
// 改为import 报错
import Koa from 'koa'
babel-node依赖包
使用 babel-node
可以在 node 端自行编译并运行
es6 甚至 es7。安装方法如下:
npm i @babel/core @babel/cli @babel/preset-env @babel/node -D
// 或者使用 yarn
注意:我这里是局部安装的,全局安装的方法请自行看官方文档。
然后我们需要在项目的根目录下面创建 .babelrc 文件
:
// .babelrc文件
{
"presets": ["@babel/preset-env"]
}
最后修改 package.json
// package.json文件 在dev和start后面加上--exec babel-node
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server --exec babel-node",
"build": "nuxt build",
"start": "cross-env NODE_ENV=production node server/index.js --exec babel-node",
"generate": "nuxt generate"
},
说明一下为什么要加 --exec
这个参数:这个参数是让 nodemon 能运行非 node 程序
比如运行 py 文件nodemon --exec “python -v” ./app.py。在这里因为我们是用 nodemon 运行 babel-node,而不是 server.js,所以需要加 --exec 这个参数。
这样就支持import语法了,可以重新跑一下服务
npm run dev
兼容sass语法
// pages/index.vue
<style lang="scss">
</style>
不处理直接使用会报错
引入两个依赖包sass-loader和node-sass
npm install sass-loader
npm install node-sass
// 如果node-sass安装失败 可以尝试使用淘宝镜像安装
// 如果没有淘宝镜像 可以先安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm install node-sass
安装好两个依赖包后,就能兼容sass语法了
入口文件
vue-cli
入口文件是app.vue
,在nuxt
开发当中则是./layout/default.vue
<template>
<div id="app">
<!-- 公共头部组件 -->
<xxx-header></xxx-header>
<!-- 路由视图,相当于router-view -->
<nuxt/>
<!-- 公共底部组件 -->
<xxx-footer></xxx-footer>
</div>
</template>
路由
Nuxt.js-路由文档
Nuxt.js 依据 pages 目录
结构自动生成
vue-router 模块的路由配置。
要在页面之间使用路由,建议使用 标签。
<template>
<nuxt-link to="/">首页</nuxt-link>
</template>
基础路由
假设 pages 的目录结构如下:
pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue
那么,Nuxt.js 自动生成的路由配置如下:
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'
}
]
}
动态路由
在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录
。
以下目录结构:
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
Nuxt.js 生成对应的路由配置表为:
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'users-id',
path: '/users/:id?',
component: 'pages/users/_id.vue'
},
{
name: 'slug',
path: '/:slug',
component: 'pages/_slug/index.vue'
},
{
name: 'slug-comments',
path: '/:slug/comments',
component: 'pages/_slug/comments.vue'
}
]
}
你会发现名称为users-id
的路由路径带有 :id? 参数
,表示该路由是可选的
。如果你想将它设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。
路由参数校验、嵌套路由、动态嵌套路由等等可以直接看官方文档,Nuxt.js-路由文档
异步数据-asyncData方法
Nuxt.js 扩展了 Vue.js,增加了一个叫asyncData 的方法
,使得我们可以在设置组件的数据之前
能异步获取或处理数据。
asyncData
方法会在组件(限于页面组件
)每次加载之前
被调用
。它可以在服务端或路由更新之前被调用。
在这个方法被调用的时候,第一个参数
被设定为当前页面的上下文对象
,你可以利用 asyncData方法来获取数据
Nuxt.js 会将 asyncData 返回的数据融合
组件 data 方法返回的数据一并返回给当前组件
。
注意:由于
asyncData方法
是在组件初始化前
被调用的,所以在方法内是没有办法通过 this 来引用
组件的实例对象。
Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:
如果项目中直接使用了node_modules中的axios,并且使用axios.interceptors添加拦截器对请求或响应数据进行了处理,确保使用 axios.create创建实例后再使用。否则多次刷新页面请求服务器,服务端渲染会重复添加拦截器,导致数据处理错误。
import axios from 'axios'
const myaxios = axios.create({
// ...
})
myaxios.interceptors.response.use(function (response) {
return response.data
}, function (error) {
// ...
})
返回 Promise
export default {
asyncData ({ params }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
}
}
使用 async或await
export default {
async asyncData ({ params }) {
const { data } = await axios.get(`https://my-api/posts/${params.id}`)
return { title: data.title }
}
}
使用 回调函数
export default {
asyncData ({ params }, callback) {
axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
callback(null, { title: res.data.title })
})
}
}
返回 对象
如果组件的数据不需要异步获取或处理,可以直接返回指定的字面对象作为组件的数据。
export default {
data () {
return { foo: 'bar' }
}
}
数据的展示
asyncData 方法返回的数据在融合 data 方法返回的数据后,一并返回给模板进行展示,如:
<template>
<h1>{{ title }}</h1>
</template>
上下文对象
可通过 API context 来了解该对象的所有属性和方法。
使用 req/res(request/response) 对象
在服务器端调用asyncData时,您可以访问用户请求的req和res对象。
export default {
async asyncData ({ req, res }) {
// 请检查您是否在服务器端
// 使用 req 和 res
if (process.server) {
return { host: req.headers.host }
}
return {}
}
}
错误处理
Nuxt.js 在上下文对象context中提供了一个 error(params) 方法,你可以通过调用该方法来显示错误信息页面。params.statusCode 可用于指定服务端返回的请求状态码。
以返回 Promise 的方式举个例子:
export default {
asyncData ({ params, error }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
}
}
如果你使用 回调函数 的方式, 你可以将错误的信息对象直接传给该回调函数, Nuxt.js 内部会自动调用 error 方法:
export default {
asyncData ({ params }, callback) {
axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
callback(null, { title: res.data.title })
})
.catch((e) => {
callback({ statusCode: 404, message: 'Post not found' })
})
}
}
Vuex状态树
对于每个大项目来说,使用状态树 (store) 管理状态 (state) 十分有必要。这就是为什么 Nuxt.js 内核实现了 Vuex。
使用状态树
Nuxt.js 会尝试找到应用根目录下的 store 目录
,如果该目录存在,它将做以下的事情:
- 引用 vuex 模块
- 将 vuex 模块 加到 vendors 构建配置中去
- 设置 Vue 根实例的 store 配置项
Nuxt.js 支持两种使用 store 的方式,你可以择一使用:
- 模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)
- Classic(不建议使用): store/index.js返回创建Vuex.Store实例的方法。.
无论使用那种模式,您的state的值
应该始终是function
,为了避免返回引用类型,会导致多个实例相互影响。
普通方式
Nuxt.js允许您拥有一个 store 目录,其中包含与模块对应的每个文件。
首先,只需将状态导出为 函数,将变量和操作作为 store/index.js 中的对象导出:
export const state = () => ({
counter: 0
})
export const mutations = {
increment (state) {
state.counter++
}
}
然后,您可以拥有 store/todos.js 文件:
export const state = () => ({
list: []
})
export const mutations = {
add (state, text) {
state.list.push({
text,
done: false
})
},
remove (state, { todo }) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, todo) {
todo.done = !todo.done
}
}
Vuex将如下创建
new Vuex.Store({
state: () => ({
counter: 0
}),
mutations: {
increment (state) {
state.counter++
}
},
modules: {
todos: {
namespaced: true,
state: () => ({
list: []
}),
mutations: {
add (state, { text }) {
state.list.push({
text,
done: false
})
},
remove (state, { todo }) {
state.list.splice(state.list.indexOf(todo), 1)
},
toggle (state, { todo }) {
todo.done = !todo.done
}
}
}
}
})
在您的 pages/todos.vue 中,使用 todos 模块:
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" :checked="todo.done" @change="toggle(todo)">
<span :class="{ done: todo.done }">{{ todo.text }}</span>
</li>
<li><input placeholder="What needs to be done?" @keyup.enter="addTodo"></li>
</ul>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
computed: {
todos () {
return this.$store.state.todos.list
}
},
methods: {
addTodo (e) {
this.$store.commit('todos/add', e.target.value)
e.target.value = ''
},
...mapMutations({
toggle: 'todos/toggle'
})
}
}
</script>
<style>
.done {
text-decoration: line-through;
}
</style>
模块方法也适用于顶级定义,而无需在 store 目录中实现子目录
示例:您创建文件 store/state.js 并添加以下内容
export default () => ({
counter: 0
})
相应的可以在文件夹中添加 store/mutations.js
export default {
increment (state) {
state.counter++
}
}
模块文件
您可以将模块文件分解为单独的文件:state.js,actions.js,mutations.js和getters.js。如果您使用index.js来维护state,getters,actions和mutations,同时具有单个单独的操作文件,那么仍然可以正确识别该文件。
注意:在使用拆分文件模块
时,必须记住使用箭头函数功能
, this 在词法上可用。词法范围this意味着它总是指向引用箭头函数的所有者。如果未包含箭头函数
,那么this将是未定义的(undefined)
。解决方案是使用 “normal” 功能,该功能会将this指向自己的作用域,因此可以使用。
fetch 方法
fetch 方法会在渲染页面前被调用
,作用是填充状态树 (store) 数据
,与 asyncData 方法类似,不同的是fetch不会设置组件的数据
。
nuxtServerInit 方法
如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象
作为第2个参数
传给它(服务端调用时才会生效)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。
举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}
如果你使用_状态树模块化_的模式,只有主模块
(即 store/index.js)适用设置该方法(其他模块设置了也不会被调用)。
nuxtServerInit 方法接收的上下文对象和 fetch 的一样,但不包括 context.redirect() 和 context.error()。
注意:异步nuxtServerInit操作必须返回Promise来通知nuxt服务器等待它们。
actions: {
async nuxtServerInit({ dispatch }) {
await dispatch('core/load')
}
}