目录
1.概述
nuxt是一个SSR服务器端渲染技术。
什么是 CSR ?
CSR => client-side-render,即客户端渲染。具体过程如下:
- 用户请求页面,返回页面。此时页面只是模版页面
- 浏览器解析页面代码,读到js代码时,会根据我们所写的接口去请求数据
- 得到返回数据后使用模版(vue/react/ng/art-template)进行渲染
什么是 SSR ?
SSR => server-side-render,即服务器端渲染。具体过程如下:
- 用户请求页面
- 后端取到准备好的数据,渲染到我们自己写的服务器模版(next/nuxt/ejs)中,准备好html结构与相应数据后返回给浏览器
2.项目结构
- assets: 资源文件。放置需要经过 webpack 打包处理的资源文件,如 scss,图片,字体等。
- components: vue组件。这里存放在页面中,可以复用的组件,不支持服务器端的钩子。
- layouts: 布局。页面都需要有一个布局,默认为 default。它规定了一个页面如何布局页面。所有页面都会加载在布局页面中的 <nuxt /> 标签中。如果需要在普通页面中使用下级路由,则需要在页面中添加 <nuxt-child />。该目录名为Nuxt.js保留的,不可更改。在 layout 中我们可以放入一些每个页面都会以用到的组件,比如 header & footer。当然如果你不想使用已生成的 layout 组件,你可以重新创建一个,比如 blank.vue 一般不需要引入 header&footer 的页面可以使用 blank.vue 这个 layout 组件。代码如下:
- layout: 'blank'
- middleware: 中间件。存放中间件。可以在页面中调用: middleware: 'middlewareName' 。
- pages: 页面。一个 vue 文件即为一个页面。index.vue 为根页面。
-
- 若需要二级页面,则添加文件夹即可。
- 如果页面的名称类似于 _id.vue (以 _ 开头),则为动态路由页面,_ 后为匹配的变量(params)。
- 若变量是必须的,则在文件夹下建立空文件 index.vue。更多的配置请移步至 官网 。
- plugin: 插件。用于组织和配置,那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件,需要配合nuxt.config.js
- static: 静态文件。放置不需要经过 webpack 打包的静态资源。如一些 js, css 库。
- store: Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。
- nuxt.config.js: nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。
3.生命周期
Vue的生命周期全都跑在客户端(浏览器),而Nuxt的生命周期在服务端(Node),客户端,甚至两边都在:
红框内的是Nuxt的生命周期首次运行在服务端,之后运行在vue组件创建之前,黄框内同时运行在服务端&&客户端上,绿框内则运行在客户端 .
nuxtServerInit
请求先到达 nuxtServerInit 方法,图中也表明了适用场景是对 store 操作,函数仅在每个服务器端渲染中运行 且运行一次,只能定义在store的主模板当中
// store/index.js
export const actions = {
nuxtServerInit(store, {app:{$cookies},route,$axios,req,res,redirect}) {
let user = $cookies.get('user') ? $cookies.get('user') : {err:2,msg:'未登录',token:''};
// store.dispatch('user/A_UPDATE_USER',user) user== store/user.js
store.commit('user/M_UPDATE_USER',user)
}
}
middleware
下来请求到达 middleware ,允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。可以运行在全局,或者某个页面组件之前,不会在components组件内部运行
//middleware/auth.js
export default ({app:{$cookies},store,redirect,route,$axios,params,query,req,res})=>{
}
//nuxt.conig.js
router: {
middleware: 'auth' //全局守卫 运行一组页面渲染之前
}
//layouts/a.vue 运行在一个布局之前
middleware(){..}, //定义在内部
middleware:'auth', //定义在外部
//pages/a.vue 运行在一个页面之前
middleware(){..}, //定义在内部
middleware:'auth', //定义在外部
//中间件执行流程顺序:
//nuxt.config.js->匹配布局->匹配页面
validate
下来请求到达 validate 方法,在这里可以对 page 组件 component 组件 进行动态路参数的有效性。返回 true 说明路由有效,则进入路由页面。返回不是 true 则显示 404 页面。
只能在页面组件使用(pages/xx.vue)
validate({ params, query }) {//参数校验,校验失败,则自动跳转到错误页面
// return /^d+$/.test(params.id) // must be number
return true;//true、false跳转方向
}
asyncData & fetch
接下来达到 asyncData & fetch 方法,asyncData() 适用于在渲染组件前获取异步数据,返回数据后合并到data选项内部,fetch() 适用于在渲染页面前填充 vuex 中维护的数据。会在组件每次加载前被调用(在服务端或切换至目标路由之前),由于是在组件 初始化 前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象。
只能在页面组件使用
//pages/a.vue
async asyncData(context){//页面组件数据预载 需要return 之后会和页面data合并
let res = await context.$axios({url:'/api/goods/home'})
return {msg2:'oo',data:res.data}//组件数据 异步的,初始的都在这里生成
}
asyncData ({ params }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
}
//将数据提交给vuex
async fetch(context){
let res = await context.$axios({url:'/api/goods/home'})
context.store.commit('XXX',res.data);//状态操作
}
render
最后进行渲染。将渲染后的页面返回给浏览器,用户在页面进行操作,如果再次请求新的页面,此时只会回到生命周期中的 middlerware 中,而非 nuxtServerInit ,所以如果不同页面间需要操作相同的数据请用 vuex 来维护,render钩子只能定义渲染时的配置,render内部不可以有业务逻辑,也不执行
beforeCreate & created
运行在服务端 & 客户端,可以获取到组件this,
mounted & updated
运行在客户端,没有keep-alive 那自然activated、deactivated这两个生命周期也没了
没有keep-alive
由于是服务端渲染,所以不支持组件的keep-alive,那自然activated、deactivated这两个生命周期也没了
不存在Window
<script>
export default {
asyncData() {
console.log(window) // 服务端报错
},
fetch() {
console.log(window) // 服务端报错
},
created () {
console.log(window) // undefined
},
mounted () {
console.log(window) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
}
}
</script>
4.路由
4.1约定式路由
展示区: <nuxt></nuxt>
声明式跳转:
<li><nuxt-link :to="{name:'shop-comment-uid',query:{a:2,b:3},params:{uid:2}}">评论2</nuxt-link></li>
- name(路由名):目录名-其他目录-文件名
- params(路由参数):要对等文件名
- query(参数):携带的参数
子路由:目录代表子路由,子路由内部同级的文件,代表是同级一级路由
4.2扩展路由
在nuxt.config.js中的router配置中添加extendRoutes函数
router:{
middleware:'auth',
//扩展路由,访问/index时访问pages/index.vue
//__dirname魔术变量:表示当前文件位置
extendRoutes(routes,resolve){
routes.push({
name:'root',
path:'/index',
component:resolve(__dirname,'pages/index.vue')
})
}
},
4.3路由守卫
前置:依赖中间件middleware插件
- 全局守卫:nuxt.config指向middleware
- 组件独享守卫:middleware,插件全局守卫
后置:使用Vue的beforeRouteLeave钩子
export default ({store,route,redirect,params,query,req,res})=>{
//context 服务端上下文
//全局守卫业务
//store状态树信息
//route 当前路由信息
//redirect 强制跳转
//params ,query校验参数合理性
console.log('middleware nuxt.config outside')
}
<template>
<div class="container">
<h1>首页</h1>
</div>
</template>
<script>
export default {
// SSR
// middleware:'auth'
middleware({store,route,redirect,params,query,req,res}){
//context 服务端上下文
//组件独享守卫业务
//store状态树信息
//route 当前路由信息
//redirect 强制跳转
//params ,query校验参数合理性
console.log('middleware nuxt.config outside')
},
4.4插件全局守卫
配置全局:通过在nuxt.config中plugins去定义一个插件,nuxt会自动调用指定文件函数
export default ({ app, redirect,store,params,query, }) => {
//ap//redirect 强制跳转p=vue实例
//redirect 强制跳转
app.router.beforEach(to,from,next)=>{
//全局前置的守卫,插件
//next()
}
//配置全局后置守卫
app.router.afterEach(to,from)=>{
}
};
配置组件独享后置守卫:在组件中配置
<template>
<div class="container">
<h1>祖册</h1>
</div>
</template>
<script>
export default {
beforeRouteLeave(to,from,netx){
let bl =window.confirm('是否要离开');
next(bl);
}
},
5.数据交互
安装:@nuxtjs/axios,@nuxt/proxy
next.config配置
modules:['@nuxtjs/axios']
//跨域配置
axios:{
proxy:true,//开启axios跨域
prefix: 'api', //beaseUrl
},
proxy:{
'/api/':{
target: 'http:/xxx', //代理地址
changeOrigin: true,
pathRewritte:{
^/api: ''
}
}
}
6.全局请求拦截器
在nuxt.config的plugins中添加配置文件
export default function ({ $axios, redirect }) {
/**
* --------------------------------------------
* 应用程序请求拦截
* --------------------------------------------
*/
$axios.onRequest((config) => {
return config;
});
/**
* --------------------------------------------
* 应用程序响应拦截
* --------------------------------------------
*/
$axios.onResponse((response) => {
return (response = response.data);
});
/**
* --------------------------------------------
* 拦截未登录错误
* --------------------------------------------
*/
$axios.onError((error) => {
const code = parseInt(error.response && error.response.status);
if (code === 401) {
// redirect("/login");
}
});
}