服务端渲染二之NUXT
在上一章服务端渲染一之相关概念中给大家整理了服务端的的相关概念,这章主要是通过对NUXT中相关配置的学习:
Nuxt.js 是什么
Nuxt的使用方式
- 初始化一个全新的项目
- 已有的Nodejs服务端项目
- 直接把Nuxt当作一个中间件集成到Node Web server中
- 现有Vue.js项目
- 非常熟悉Nuxt.js
- 至少百分之10的代码改动
- 如何初始化Nuxt项目
初始化Nuxt项目
我们可以使用两种方式进行创建:具体创建方式见官方文档 :
- 使用create-nuxt-app创建
- 手动创建
路由学习
项目创建完成之后我们首先开始学习NUXT的路由:
- 基础路由
假设 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/router.js
-
路由导航
- a标签 不推荐 会刷新整个页面
<!-- 页面刷新 走服务端渲染 重新首屏直出 --> <a href="/">首页</a>
- nuxt-link 与Vue-router基本一致
<!-- 页面不刷新 spa单页面体验 --> <nuxt-link to="/"></nuxt-link>
- 编程式导航 与Vue-router基本一致
<!-- 页面不刷新 spa单页面体验 --> <template> <button @click="onClick">首页</button> </template> <script> export default{ ... methods:{ onClick(){ this.$router.push('/') } } } </script>
-
动态路由 与vueRouter动态路由基本一致
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 可以让你在动态路由组件中定义参数校验方法。
举个例子: pages/users/_id.vue
export default {
validate({ params }) {
// 必须是number类型
return /^\d+$/.test(params.id)
}
}
如果校验方法返回的值不为 true或Promise中 resolve 解析为false或抛出 Error , Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。
- 嵌套路由
pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
Nuxt.js 自动生成的路由配置如下:
router: {
routes: [
{
path: '/users',
component: 'pages/users.vue',
children: [
{
path: '',
component: 'pages/users/index.vue',
name: 'users'
},
{
path: ':id',
component: 'pages/users/_id.vue',
name: 'users-id'
}
]
}
]
}
在'pages/users.vue
需要在特定位置放置<nuxt-child></nuxt-child>
作为子路由出口,
子路由组件将渲染在该位置
-
base 应用的根 URL。举个例子,如果整个单页面应用的所有资源可以通过 /app/ 来访问,那么 base 配置项的值需要设置为 ‘/app/’。
-
extendRoutes
```js //添加自定义路由: 此时/hello路径同样指向 about组件 export default { router: { extendRoutes(routes, resolve) { routes.push({ name: '/hello', path: 'hello', component: resolve(__dirname, 'pages/about.vue') }) } } } ```
NUXT的视图
NUXT模板
你可以定制化 Nuxt.js 默认的应用模板。
定制化默认的 html 模板,只需要在 src 文件夹下(默认是应用根目录)创建一个 app.html
的文件。
默认模板为:
布局 公共布局
Nuxt.js 允许你扩展默认的布局,或在 layout
目录下创建自定义的布局。
可通过添加 layouts/default.vue
文件来扩展应用的默认布局。
提示: 别忘了在布局文件中添加 <nuxt/>
组件用于显示页面的主体内容。
默认布局的源码如下:
<template>
<nuxt />
</template>
可以在组件中通过layout属性修改默认布局组件:
template>
<!-- Your template -->
</template>
<script>
export default {
layout: '布局名称' //不写表示使用默认布局 layout中的default文件
// page component definitions
}
</script>
Index页面的布局组件变成了foo,但是about页面还是default,因为about页面没有修改其layout属性,所以默认的布局文件还是default
异步数据 asyncData
- 基本用法
- 用于服务端渲染的数据 (服务端调用)
- 客户端路由导航之前会执行(此时是客户端调用)
- 它会将asyncdata返回的数据融合组件data方法返回数据一并给组件
- 注意事项
- 只能在页面组件使用
- 没有this,因为在组件初始化之前调用
- 当你想要动态页面内容有利于seo或者提升首屏渲染速度时,在asyncData中发请求拿数据
- 如果时非异步数据或者普通数据,则正常的初始化到data中即可(data中初始数据同样会被首屏直出到页面)
异步数据 上下文对象
-
https://zh.nuxtjs.org/guide/async-data#%E4%B8%8A%E4%B8%8B%E6%96%87%E5%AF%B9%E8%B1%A1
-
asyncData的参数为上下文对象,我们无法在这个方法里使用this,所以无法通过this.$router.params.id拿到路由参数,但是可以通过context.params.id获取参数
pages/article/_id.vue
<template>
<div>
<h1>article Page </h1>
<nuxt-link to="/">首页</nuxt-link>
<h3>title: {{post.title}}</h3>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'ArticlePage',
async asyncData (context) {
// asyncData的参数为上下文对象,,我们无法在这个方法里使用this,
//所以无法通过this.$router.params.id拿到路由参数,但是可以通过context.params.id获取参数
console.log(context)
const { data: {posts} } = await axios({
method: 'GET',
url: 'http://localhost:3000/data.json'
})
// 这里的请求地址要写完整,因为在服务端渲染期间,也要来请求数据,不写完整的话服务端渲染就会走到80端口,如果只是客户端渲染,就会以3000端口为基准来请求根目录下的data.json,服务端渲染就默认走到80了
const id = parseInt(context.params.id, 10)
return {
post: posts.find(item => item.id === id),
}
}
}
</script>
登录状态持久化
将数据存到store中,只是在内存里,页面一刷新就没了。所以我们应该想办法将数据进行持久化。以前的做法是存到本地存储里,而现在在服务端也要渲染,所以不可以存在本地存储,否则服务端获取不到。正确的做法是存在cookie中,cookie可以随着http请求发送到服务端。
所以在login/index.vue页面中容器保存完登录状态后,还要将数据存储到Cookie中
// 仅在客户端加载js-cookie
const Cookie = process.client ? require('js-cookie'): undefined
// ...
// 保存用户的登录状态
this.$store.commit('setUser', data.user)
// 为了防止刷新页面数据丢失,数据需要持久化
//把登录状态存储到cookie 放置页面刷新丢失数据
//cookie信息客户端服务端都能使用
//本地存储的信息不会带到服务端
Cookie.set('user', data.user)
在Store/index.js的actions
中增加nuxtServerInit
方法,nuxtServerInit
是一个特殊的action
方法,
这个方法会在服务端渲染期间自动调用,作用是初始化容器数据,传递数据给客户端使用
export const actions = {
//初始化容器以及需要传递给客户端的数据
//该方法只会在服务端渲染期间自动调用
nuxtServerInit ({ commit }, { req }) {
let user = null
// 如果请求头中有个Cookie
if (req.headers.cookie) {
// 使用Cookieparser把cookie字符串转化为JavaScript对象
const parsed = cookieparser.parse(req.headers.cookie)
try {
user = JSON.parse(parsed.user)
} catch (err) {
// No valid cookie found
}
}
commit('setUser', user)
}
}
处理页面访问权限 – 中间件
https://zh.nuxtjs.org/guide/routing#%E4%B8%AD%E9%97%B4%E4%BB%B6
中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。
每一个中间件应放置在 middleware/
目录。文件名的名称将成为中间件名称 (middleware/auth.js
将成为 auth
中间件)。然后给要保护的页面增加middleware
属性,值为中间件的文件名
中间件执行流程顺序:
nuxt.config.js
- 匹配布局
- 匹配页面
定义两个中间件,
middleware/authenticated.js
export default function ({ store, redirect }) {
// 如果用户未登录,则跳转到登录页
if (!store.state.user) {
return redirect('/login')
}
}
middleware/notAuthenticated.js
export default function ({ store, redirect }) {
// 如果用户已登录,则跳转到首页
if (store.state.user) {
return redirect('/')
}
}
然后给settings/index.vue
、profile/index.vue
、editor/index
页面增加属性middleware
值为authenticated
,当用户未登录,则跳转到登录页
export default {
name: 'Settings',
middleware: 'authenticated'
}
给login/index.vue
页面增加属性middleware
值为notAuthenticated
,如果用户已登录,则跳转到首页
export default {
name: 'LoginPage',
middleware: 'notAuthenticated',
// ...
}
插件配置(这里以axios拦截器为例)
axios请求拦截器:
https://github.com/axios/axios#interceptors
Nuxt中插件:
https://zh.nuxtjs.org/guide/plugins#%E6%B3%A8%E5%85%A5-context
新建文件plugins/request.js
/**
* 基于axios封装的请求模块
*/
import axios from 'axios'
// 创建请求对象
export const request = axios.create({
baseURL: 'https://conduit.productionready.io'
})
// 通过插件机制获取到上下文对象(query、params、req、res、app、store···)
// 插件导出函数必须作为default成员
//不作为插件导出获取不到上下文对象
export default ({ store }) => {
// 请求拦截器
// 任何请求都要经过请求拦截器
// 我们可以在请求拦截器中做一些公共的业务处理,例如统一设置Token
request.interceptors.request.use(function (config) {
// 请求就会经过这里
const { user } = store.state
if ( user && user.token)
config.headers.Authorization = `Token ${user.token}`
// 返回config请求配置对象
return config;
}, function (error) {
// 如果请求失败(此时请求还没有发出去)就会进入这里
// Do something with request error
return Promise.reject(error);
});
}
在Nuxt配置文件中注册插件:
Nuxt.config.js
module.exports = {
// 注册插件
plugins: [
'~/plugins/request.js', // 波浪线开头表示从根路径触发
]
}
NuxtJS综合案例(实现了一个简单的NUXT案例)
案例介绍
- RealWorld是一个开源的学习项目,目的就是帮助开发者快速学习新技能。
GitHub仓库:https://github.com/gothinkster/realworld
在线实例:https://demo.realworld.io/
案例资源
- 页面模板:https://github.com/gothinkster/realworld-starter-kit/blob/master/FRONTEND_INSTRUCTIONS.md
- 接口文档:https://github.com/gothinkster/realworld/tree/master/api