服务端渲染SSR


资源


  1. vue ssr
  2. nuxt.js

知识点


Nuxt.js实战

Nuxt.js 是一个基于 Vue.js 的 通用应用框架
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染

结论:

  1. nuxt不仅仅用于服务端渲染也可用于spa应用开发;
  2. 利用nuxt提供的基础项目结构、路由生成、中间件、插件等特性可大幅提高开发效率
  3. nuxt可用于网站静态化

资源

Nuxt.js官方文档

nuxt.js特性

  • 代码分层
  • 服务端渲染
  • 强大的路由功能
  • 静态文件服务

nuxt渲染流程

一个完整的服务器请求到渲染的流程
image.png

nuxt安装

运行 create-nuxt-app

  1. npx create-nuxt-app <项目名>

选项
image.png

运行项目:npm run dev

目录结构

  • assets: 资源目录 assets 用于组织未编译的静态资源如 LESSSASSJavaScript
  • components: 组件目录 components 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像⻚面组件那样有 asyncData 方法的特性。
  • layouts: 布局目录 layouts 用于组织应用的布局组件。
  • middleware: 中间件目录 用于存放应用的中间件。
  • pages: ⻚面目录 pages 用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue文件并自动生成对应的路由配置。
  • plugins: 插件目录 plugins 用于组织那些需要在 根vue.js应用 实例化之前需要运行的Javascript 插件。
  • static: 静态文件目录 static 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
  • store:用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。
  • nuxt.config.js:该文件用于 个性化配置 Nuxt应用。

路由

路由生成

pages目录中所有 *.vue 文件自动生成应用的路由配置,新建:

  • pages/admin.vue 商品管理⻚
  • pages/login.vue 登录⻚

导航

添加路由导航,layouts/default.vue

  1. <nav>
  2. <nuxt-link to="/">首⻚</nuxt-link>
  3. <!--别名:n-link,NLink,NuxtLink-->
  4. <NLink to="/admin">管理</NLink>
  5. <n-link to="/cart">购物⻋</n-link>
  6. </nav>

商品列表,index.vue

  1. <template>
  2. <div>
  3. <h2>商品列表</h2>
  4. <ul>
  5. <li v-for="good in goods" :key="good.id">
  6. <nuxt-link :to="`/detail/${good.id}`">
  7. <span>{{good.text}}</span>
  8. <span>¥{{good.price}}</span>
  9. </nuxt-link>
  10. </li>
  11. </ul>
  12. </div>
  13. </template>
  14. <script>
  15. export default {
  16. data () {
  17. return {
  18. goods: [
  19. { id: 1, text: 'Web全栈架构师', price: 8999 },
  20. { id: 2, text: 'Python全栈架构师', price: 8999 },
  21. ]
  22. }
  23. }
  24. };
  25. </script>

动态路由

以下划线作为前缀 的 .vue文件 或 目录会被定义为动态路由,如下面文件结构

  1. pages/
  2. --| detail/
  3. ----| _id.vue

会生成如下路由配置:

  1. {
  2. path: "/detail/:id?",
  3. component: _9c9d895e,
  4. name: "detail-id"
  5. }

如果detail/里面不存在index.vue,:id将被作为可选参数

嵌套路由

创建内嵌子路由,你需要添加一个 .vue 文件,同时添加一个 与该文件同名 的目录用来存放子视图组件。

构造文件结构如下:

  1. pages/
  2. --| detail/
  3. ----| _id.vue
  4. --| detail.vue

生成的路由配置如下:

  1. {
  2. path: '/detail',
  3. component: 'pages/detail.vue',
  4. children: [
  5. {path: ':id?', name: "detail-id"}
  6. ]
  7. }

测试代码,detail.vue

  1. <template>
  2. <div>
  3. <h2>detail</h2>
  4. <nuxt-child></nuxt-child>
  5. </div>
  6. </template>

nuxt-child等效于router-view

配置路由

要扩展Nuxt.js创建的路由,可以通过router.extendRoutes选项配置。例如添加自定义路由:

  1. // nuxt.config.js
  2. export default {
  3. router: {
  4. extendRoutes (routes, resolve) {
  5. routes.push({
  6. name: "foo",
  7. path: "/foo",
  8. component: resolve(__dirname, "pages/custom.vue")
  9. });
  10. }
  11. }
  12. }

视图

下图展示了Nuxt.js 如何为指定的路由配置数据和视图
image.png

默认布局

查看layouts/default.vue

  1. <template>
  2. <nuxt/>
  3. </template>

自定义布局

创建空白布局⻚面layouts/blank.vue,用于login.vue

  1. <template>
  2. <div>
  3. <nuxt />
  4. </div>
  5. </template>

⻚面pages/login.vue使用自定义布局:

  1. export default {
  2. layout: 'blank'
  3. }

自定义错误⻚面

创建layouts/error.vue

  1. <template>
  2. <div class="container">
  3. <h1 v-if="error.statusCode === 404">⻚面不存在</h1>
  4. <h1 v-else>应用发生错误异常</h1>
  5. <p>{{error}}</p>
  6. <nuxt-link to="/">首 ⻚</nuxt-link>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. props: ['error'],
  12. layout: 'blank'
  13. }
  14. </script>

⻚面

⻚面组件就是 Vue 组件,只不过 Nuxt.js 为这些组件添加了一些特殊的配置项
给首⻚添加标题和meta等,index.vue

  1. export default {
  2. head () {
  3. return {
  4. title: "课程列表",
  5. // vue-meta利用hid确定要更新meta
  6. meta: [{
  7. name: "description", hid: "description", content: "set page meta"
  8. }],
  9. link: [{ rel: "favicon", href: "favicon.ico" }],
  10. };
  11. },
  12. };

更多⻚面配置项:

属性名 描述
asyncData 最重要选项, 支持 异步数据处理,该方法的第一个参数为当前⻚面组件的 上下文对象。
fetch asyncData 方法类似,用于在渲染⻚面之前获取数据填充应用的状态树(store)。不同的是 fetch 方法不会设置组件的数据。详情请参考关于fetch方法的文档
head 配置当前⻚面的 Meta 标签, 详情参考 ⻚面头部配置API
layout 指定当前⻚面使用的布局(layouts 根目录下的布局文件)。详情请参考 关于 布局 的文档
loading 如果设置为false,则阻止⻚面自动调用this.$nuxt.$loading.finish()this.$nuxt.$loading.start(),您可以手动控制它,请看例子,仅适用于在nuxt.config.js中设置loading的情况下。请参考API配置 文档loading
transition 指定⻚面切换的过渡动效, 详情请参考 ⻚面过渡动效
scrollToTop 布尔值,默认: false。 用于判定渲染⻚面前是否需要将当前⻚面滚动至顶部。这个配置用于 嵌套路由的应用场景。
validate 校验方法用于校验 动态路由的参数。
middleware 指定⻚面的中间件,中间件会在⻚面渲染之前被调用, 请参考 路由中间件

异步数据获取

asyncData 方法使得我们可以在 设置组件数据之前异步获取或处理数据

范例:获取商品数据

接口准备

  • 安装依赖:npm i koa-router koa-bodyparser -S
  • 接口文件,server/api.js

整合axios

安装@nuxt/axios模块:npm install @nuxtjs/axios -S

win10有时需管理员权限启动vscode

配置:nuxt.config.js

  1. modules: [
  2. '@nuxtjs/axios',
  3. ],
  4. axios: {
  5. proxy: true
  6. },
  7. proxy: {
  8. "/api": "http://localhost:8080"
  9. },

注意配置重启生效

测试代码:获取商品列表,index.vue

  1. <script>
  2. export default {
  3. async asyncData ({ $axios, error }) {
  4. const { ok, goods } = await $axios.$get("/api/goods");
  5. if (ok) {
  6. return { goods };
  7. }
  8. // 错误处理
  9. error({ statusCode: 400, message: "数据查询失败" });
  10. },
  11. }
  12. </script>

测试代码:获取商品详情,/index/_id.vue

  1. <template>
  2. <div>
  3. <pre v-if="goodInfo">{{goodInfo}}</pre>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. async asyncData ({ $axios, params, error }) {
  9. if (params.id) {
  10. // asyncData中不能使用this获取组件实例
  11. // 但是可以通过上下文获取相关数据
  12. const { data: goodInfo } = await $axios.$get("/api/detail", { params });
  13. if (goodInfo) {
  14. return { goodInfo };
  15. }
  16. error({ statusCode: 400, message: "商品详情查询失败" });
  17. } else {
  18. return { goodInfo: null };
  19. }
  20. }
  21. };
  22. </script>

中间件

中间件会在一个⻚面或一组⻚面渲染之前运行我们定义的函数,常用于权限控制、校验等任务。

范例代码:管理员⻚面保护,创建middleware/auth.js

  1. export default function ({ route, redirect, store }) {
  2. // 上下文中通过store访问vuex中的全局状态
  3. // 通过vuex中令牌存在与否判断是否登录
  4. if (!store.state.user.token) {
  5. redirect("/login?redirect=" + route.path);
  6. }
  7. }

注册中间件,admin.vue

  1. <script>
  2. export default {
  3. middleware: ['auth']
  4. }
  5. </script>

全局注册:将会对所有⻚面起作用,nuxt.config.js

  1. router: {
  2. middleware: ['auth']
  3. },

状态管理 vuex

应用根目录下如果存在 store 目录,Nuxt.js将启用vuex状态树。定义各状态树时具名导出state,mutations, getters, actions即可。

范例:用户登录及登录状态保存,创建store/user.js

  1. export const state = () => ({
  2. token: ''
  3. });
  4. export const mutations = {
  5. init (state, token) {
  6. state.token = token;
  7. }
  8. };
  9. export const getters = {
  10. isLogin (state) {
  11. return !!state.token;
  12. }
  13. };
  14. export const actions = {
  15. login ({ commit, getters }, u) {
  16. return this.$axios.$post("/api/login", u).then(({ token }) => {
  17. if (token) {
  18. commit("init", token);
  19. }
  20. return getters.isLogin;
  21. });
  22. }
  23. };

登录⻚面逻辑,login.vue

  1. <template>
  2. <div>
  3. <h2>用户登录</h2>
  4. <el-input v-model="user.username"></el-input>
  5. <el-input type="password" v-model="user.password"></el-input>
  6. <el-button @click="onLogin">登录</el-button>
  7. </div>
  8. </template>
  9. <script>
  10. export default {
  11. data () {
  12. return {
  13. user: {
  14. username: "",
  15. password: ""
  16. }
  17. };
  18. },
  19. methods: {
  20. onLogin () {
  21. this.$store.dispatch("user/login", this.user).then(ok => {
  22. if (ok) {
  23. const redirect = this.$route.query.redirect || '/'
  24. this.$router.push(redirect);
  25. }
  26. });
  27. }
  28. }
  29. };
  30. </script>
  1. ```<br />
  1. <a name="94L7h"></a>
  2. ### 插件
  3. Nuxt.js会在运行应用之前执行插件函数,需要引入或设置Vue插件、自定义模块和第三方模块时特别有用。
  4. 范例代码:接口注入,利用插件机制将服务接口注入组件实例、store实例中,创建plugins/api-inject.js
  5. ```typescript
  6. export default ({ $axios }, inject) => {
  7. inject("login", user => {
  8. return $axios.$post("/api/login", user);
  9. });
  10. };

注册插件,nuxt.config.js

  1. plugins: [
  2. "@/plugins/api-inject"
  3. ],

范例:添加请求拦截器附加token,创建plugins/interceptor.js

  1. export default function ({ $axios, store }) {
  2. $axios.onRequest(config => {
  3. if (store.state.user.token) {
  4. config.headers.Authorization = "Bearer " + store.state.user.token;
  5. }
  6. return config;
  7. });
  8. }

注册插件,nuxt.config.js

  1. plugins: ["@/plugins/interceptor"]

nuxtServerInit

通过在store的根模块中定义 nuxtServerInit 方法,Nuxt.js 调用它的时候会将⻚面的上下文对象作为第 2 个参数传给它。当我们想将服务端的一些数据传到客户端时,这个方法非常好用。

范例:登录状态初始化,store/index.js

  1. export const actions = {
  2. nuxtServerInit ({ commit }, { app }) {
  3. const token = app.$cookies.get("token");
  4. if (token) {
  5. console.log("nuxtServerInit: token:" + token);
  6. commit("user/init", token);
  7. }
  8. }
  9. };
  • 安装依赖模块:cookie-universal-nuxt
  1. npm i -S cookie-universal-nuxt

注册, nuxt.config.js

  1. modules: ["cookie-universal-nuxt"],
  • nuxtServerInit只能写在store/index.js
  • nuxtServerInit仅在服务端执行

发布部署

服务端渲染应用部署

先进行编译构建,然后再启动 Nuxt 服务

  1. npm run build
  2. npm start

生成内容在.nuxt/dist中

静态应用部署

Nuxt.js 可依据路由配置将应用静态化,使得我们可以将应用部署至任何一个静态站点主机服务商。

  1. npm run generate

注意渲染和接口服务器都需要处于启动状态 生成内容再dist中

inject的使用

image.png
参考:inject的使用

axios的使用

image.png
参考:axios的使用