上次和你们分享了VUE同构赋能 VUE SSR 篇,连接在这里
juejin.cn/post/684490…javascript
今天是下篇:NUXT
上篇有可能讲的太复杂了😂,此次咋们直奔主题
仍是上次的 DEMO,咱们用NUXT来实现这个页面
github地址在文章末尾 html
Nuxt是基于Vue ssr之上,集成了Vue-Router,Vuex,webpack等框架、组件的一个服务端渲染框架
在我看来,Nuxt就是一个升级版的Vue ssr,为咱们预设了服务端渲染的应用所须要的各类配置
可是相应的,Nuxt的入侵性是特别高的,咱们须要理解Nuxt的思路,才能发挥它的优点。前端
首先咱们来看下Nuxt项目的目录结构
vue
具体文件夹能够看到java
assets - 前端资源文件
components - 前端通用组件
layouts - 前端布局组件
store - vuex
static - 静态文件webpack
plugins - 插件
middleware - 中间件ios
pages - 前端VUE页面git
server - 后端github
其实这么多文件,能够分为三个:前端,后端,资源web
Nuxt中,处处体现着"约定大于配置"的思想,这一点咱们要注意
好比最简单的,上面目录文件夹的名字,若是没有额外配置,是不能更更名字的
还有例如:layouts布局文件中
default.vue是默认布局页
error.vue是默认错误页面
好比pages前端页面文件夹中,约定了自动生成router的规则
还有store文件夹中,默认index.js是默认状态树,
todos.js 文件则会自动生成todos 模块
等等……
这些具体的下面会具体讲到
如今开始编写咱们的demo, 先从layout布局页开始,咱们须要写两个页面default.vue和error.vue
布局页就能够看做咱们平时 vue 的 app.vue 入口页面
可是他们有不一样之处,Nuxt的子页面内,能够指定布局页(未指定就是default.vue)
default.vue
<template> <div> <header> <div class="head-content"> <section class="head-left"> 一个帮助开发者成长的社区 | <nuxt-link to="/"> 首页</nuxt-link> </section> <section class="head-right"> <nuxt-link to="/user/login"> 登陆</nuxt-link> | <nuxt-link to="/user/register"> 注册</nuxt-link> </section> </div> </header> <nuxt /> </div> </template> 复制代码
子页面设置layout
export default { // 默认就是default layout: "default", } 复制代码
咱们在布局页内能够看到<nuxt>与<nuxt-link> 这两个组件,有点似曾相识的感受
其实就是对应的<router-view><router-link>,
因此布局页或者咱们的嵌套路由的页面,都须要用到<nuxt>组件
咱们能够看到源码内:
// nuxt let routerView = [ h('router-view', data) ] if (props.keepAlive) { routerView = [ h('keep-alive', { props: props.keepAliveProps }, routerView) ] } return h('transition', { props: transitionProps, on: listeners }, routerView) // nuxt-link export default { name: 'NuxtLink', extends: Vue.component('RouterLink'), } 复制代码
是扩展了vue-router
其中<nuxt>可接受接收 keep-alive 和 keep-alive-props
<nuxt-link>则是帮咱们扩展了自动预获取代码分割页面
可使用 no-prefetch属性 禁用
<nuxt-link to="/user" no-prefetch>我的中心</nuxt-link> 复制代码
路由在Nuxt中全是自动构建的,这点很是有用,会帮咱们减小不少配置工做
在全部的SSR项目中,其实要作到先后端同构,都是依赖路由来断定先后端的入口,从而进行输出的
咱们来看下Nuxt构建路由的规则
简单来讲,我总结了三点:
咱们根据咱们的demo来看看,咱们须要我的中心和列表页
因此咱们的目录结构是这样的
最终编译的router.js以下:
routes: [{ path: "/user/login", component: _fade0492, name: "user-login" }, { path: "/user/register", component: _35d3a876, name: "user-register" }, { path: "/info/:pageIndex?", component: _287821ee, name: "info-pageIndex" }, { path: "/", component: _48b188ea, name: "index" }] 复制代码
能够看到,列表页生成了/info/:pageIndex? 这个路由
咱们经过 /info/1或者/info/2 能够访问到对应页码的数据
咱们再来看看 _pageIndex.vue
<template> <div> <vmenu type="info" /> <div class="info-container"> <div class="info-title">热门信息</div> <ul class="info-content"> <li v-for="item in list" :key="item.title"> <section class="info-content-r">{{ item.publishDate }}</section> <section class="info-content-l">{{ item.title }}</section> </li> </ul> <vpage :count="count" url="info" :pageIndex="pageIndex" /> </div> </div> </template> 复制代码
page.vue
<template> <div class="page-container"> <section> <router-link :to="{ name: toUrl, params: { pageIndex: 1 } }"> 第一页 </router-link> </section> <section v-for="(i, ix) in pageCount" :key="i" :class="{ current: pageIndex == ix + 1 }" > <router-link :to="{ name: toUrl, params: { pageIndex: ix + 1 } }"> {{ ix + 1 }} </router-link> </section> <section> <router-link :to="{ name: toUrl, params: { pageIndex: pageCount } }"> 最后一页 </router-link> </section> </div> </template> <script> export default { name: "page", props: { pageSize: { default: 10 }, pageIndex: { default: 1 }, count: { default: 100 }, url: { default: "" } }, computed: { toUrl() { return this.url + "-pageIndex"; }, pageCount() { return this.count % this.pageSize == 0 ? this.count / this.pageSize : Math.floor(this.count / this.pageSize) + 1; } }, mounted() {}, created() {} }; </script> 复制代码
能够看到由于自动构建的路由名称是 info-pageIndex
因此咱们分页跳转的时候也须要对应的拼接好路由
异步数据获取是SSR项目必须的功能,在Nuxt中至关简便
asyncData很眼熟,VUR SSR中也有一样的方法用来加载异步数据
可是,可是
在Nuxt中的asyncData方法是彻底不同的
在这里,不只能获取异步的数据,并且还会将最终的数据挂载到data上
咱们看下demo这里,从后台获取一下新闻列表:
export default { async asyncData({ isDev, route, store, env, params, query, req, res, redirect, error, $axios }) { let data = await $axios.post(`/api/news/list`, { page: params.pageIndex || 1 }); return { ...data.data }; }, components: { vmenu: Menu, vpage: page }, // router.path是不同的,不用监听 // watchQuery: ["pageIndex"], data() { return { pageIndex: this.$route.params.pageIndex || 1 }; } }; 复制代码
这里axios必定要使用Nuxt官方模块@nuxt/axios,会自动进行header填充等其余优化
总之,就是这么简单,咱们再async内获取数据,而后return出来就都OK了
以后组件内就和咱们平时在data内使用数据同样了
再来看看fetch,其实fetch就和Vue SSR中的asyncData很像了
它不会绑定在data内,只是用来操做store的 用法以下:
<template> <h1>Stars: {{ $store.state.stars }}</h1> </template> <script> export default { fetch ({ store, params }) { return axios.get('http://my-api/stars') .then((res) => { store.commit('setStars', res.data) }) } } </script> 复制代码
相较VUESSR,Nuxt中的异步数据更加纯粹,不须要借助VUEX就能够实现
store回归状态共享的本质
咱们再来看看在Nuxt中vuex是怎么使用的
其实开始也说了,Nuxt约定在store目录中index.js是默认状态树
其余名称的文件,会生成对应的store模块
因此咱们来写一个保存登陆状态的store store/user.js
export const state = () => { user: null }; export const mutations = { // 设置登陆用户 set(state, user) { state.user = user; }, loginOut(state) { state.user = null; } } 复制代码
有个问题,咱们何时把登陆状态写进store呢?
想一想平时咱们的应用,写进cookie或者localstorage是比较好的方案
可是咱们SSR项目有个优点,咱们能够紧密的和后端结合起来
咱们可使用 nuxtServerInit 将客户的登陆状态写进sotre
注意,nuxtServerInit只有服务端调用时才会执行
store/index.js
export const actions = { nuxtServerInit({ commit }, { req }) { if (req.session.user) { commit("user/set", req.session.user); } } } 复制代码
这样就能一直维护客户的最新登陆状态(固然前端登录成功后,也要写入store)
那么前端页面怎么判断登陆状态呢?
在每一个路由切换的时候判断比较好,好比在router.beforeEach内
可是Nuxt路由是自动构建的,也没有相关接口
可是别急,咱们有更好用的 middleware
中间件能够在咱们指定的页面加载以前执行,而且能够执行异步执行
布局页内能够设置中间件,这样全部该布局的页面,都会执行该中间件
来看看demo的例子 middleware/default.js
export default function ({ route, store, redirect }) { // 若是用户不存在,跳到登陆页面 if (!store.state.user.user) redirect('/user/login?redirect=' + route.path); } 复制代码
_pageIndex.vue页面内引用一下该中间件
middleware: "default" 复制代码
引用以后,若是未登陆就会自动跳转到登陆页
Nuxt有丰富的模块能够供咱们使用
官方模块有:
@nuxt/http: 基于ky-universal的轻量级和通用的HTTP请求
@nuxtjs/axios: 安全和使用简单Axios与Nuxt.js集成用来请求HTTP
@nuxtjs/pwa: 使用通过严格测试,更新且稳定的PWA解决方案来加强Nuxt
@nuxtjs/auth: Nuxt.js的身份验证模块,提供不一样的方案和验证策略
社区模块就更多了:
github.com/topics/nuxt…
使用方法也很简单,直接在nuxt.config.js内 好比:
modules: [ ['@nuxtjs/axios', { baseURL: 'http://localhost:3000' }], ], 复制代码
固然也还仍是会本身引用一些插件的,咱们可使用plugins配置
plugins: [ { src: '@/plugins/element-ui', ssr: false } ], 复制代码
注意,若是咱们的插件只能在服务端运行的话,能够配置ssr:false,而后前端也能够配合<no-ssr>组件来使用
接着在plugins目录内进行扩展,这里就暴露了Vue对象给咱们
import Vue from 'vue' import Element from 'element-ui' import locale from 'element-ui/lib/locale/lang/en' Vue.use(Element, { locale }) 复制代码
到这里,咱们的demo页面就写完了
要值得注意的是Nuxt生命周期的差别
Nuxt扩展的asyncData,fetch,validate,middleware会在先后端内都执行
nuxtServerInit,serverMiddleware会在服务端执行
暴露给咱们的redirect,error等方法先后端均可以使用,
可是诸如req,res之类的,前端则为undefined,这也是须要时刻注意的
最后我们再来看看缓存
组件缓存和VUESSR同样,咱们在nuxt.config.js内配置缓存项
const LRU = require('lru-cache') module.exports = { render: { bundleRenderer: { cache: LRU({ max: 1000, maxAge: 1000 * 60 * 15 }) } } } 复制代码
接着组件内指定好serverCacheKey就OK
export default { props: ['type'], serverCacheKey: props => props.type } 复制代码
页面级别的缓存的话,有点特殊,咱们须要使用Nuxt的服务端中间件
这里咱们再以上次讲的redis-lru来作页面缓存
const config = require('../nuxt.config.js') module.exports = async function (req, res, next) { if (process.env.NODE_ENV === "development") { next(); return; } const redis = require('redis').createClient(config.redis); const lru = require('redis-lru'); const cache = lru(redis, 100); // 获取缓存的key值 let key = req.originalUrl const value = await cache.get(key) if (value) { console.log('cached output', key); return res.end(value, 'utf-8') } res._end = res.end res.end = async function (data) { if (res._headers['content-type'].startsWith('text/html') && res.statusCode === 200) { await cache.set(key, data) } res._end(data, 'utf-8') } next(); } 复制代码
值得注意的是服务端中间件中的req和res是 Node.js http request 对象
最后输出须要调用res的原始end方法(就算你Nuxt配置的后端服务器为koa)
好了,个人VUE SSR系列上下篇都讲完了
Nuxt相较VUESSR是一个升级版,若是你是新启动项目,建议直接从Nuxt开始
若是想体验一下SSR全流程,能够从头开始配置VUESSR,会有不同的体验
本次Nuxt的demo地址在这里: github.com/kungithub/s…