Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue Node服务端渲染之Nuxt #44

Open
Hancoson opened this issue Aug 20, 2020 · 0 comments
Open

Vue Node服务端渲染之Nuxt #44

Hancoson opened this issue Aug 20, 2020 · 0 comments

Comments

@Hancoson
Copy link
Owner

Nuxt.js 简单介绍

Nuxt.js 是一个基于 Vue.js 的服务端渲染框架。

Vue SSR 的原理图

原理图

Nuxt.js的优势

  • 无需为了路由划分而烦恼,你只需要按照对应的文件夹层级创建 .vue 文件就行
  • 无需考虑数据传输问题,Nuxt 会在模板输出之前异步请求数据(需要引入 axios 库),而且对 vuex 有进一步的封装
  • 内置了 webpack,省去了配置 webpack 的步骤,nuxt 会根据配置打包对应的文件

下图阐述了 Nuxt.js 应用一个完整的服务器请求到渲染(或用户通过 <nuxt-link> 切换路由渲染页面)的流程:
流程

  • Incoming Request:浏览器发出一个请求

  • nuxtServerInit:服务端接收到请求,检查当前有没有nuxtServerInit这个配置项,如果有的化就先执行这个方法,这个方法是用来操作vuex的

  • middleware:这是一个中间件,跟路由相关,这里可以做任何想要的功能

  • validate():验证,配合高级动态路由去做一些验证,比如A页面是否允许跳转到B页面去,如果没有验证通过可以跳转到别的页面之类的校验

  • asyncData()&fetch():获取数据。asyncData()获取的数据是用来渲染vue组件的,fetch通常用来修改vuex的数据

  • Render:渲染

  • Naviage:如果发起了一个非Server 的路由,那么在执行一遍middleware——Render的过程

主要说明

asyncData

asyncData方法是 Nuxt.js 对 Vue 扩展的方法。 会在组件(限于页面组件)每次初始化前被调用的。所以该方法没有办法通过this来引用组件的实例对象。它可以在服务端或路由更新之前被调用。可以利用asyncData方法来获取数据并返回给当前组件。

asyncData (context) {
  return { project: 'nuxt' }
}

asyncData 的参数(context)有哪些?

  • app:app 对象
  • store:存储,可以拿到store里的数据
  • route:路由,可以拿到参数之类的
  • params:url 路径参数,主要获取id
  • query:可以理解为url上问号后面的参数
  • env:运行环境
  • isDev:是否是开发环境
  • isHMR:是否是热更新
  • redirect:重定向
  • error:错误

asyncData 可以做什么?

创建渲染器

Nuxt renderer 使用vue-server-renderer插件创建渲染器并解析 webpack 打好的 bundle 文件

fetch

fetch方法用于渲染页面前填充应用的状态树(store)数据,与asyncData方法类似,不同的是它不会设置组件的数据。

async fetch({ store, params }) {
  const { data } = axios.get('http://abc.com/api/stara')
  store.commit('setStars/set', data)
}

fetch或者asyncData这2个方法在layouts和components中是失效的。

server.js做了哪些事情

每个用户通过浏览器访问 Vue 页面时,都是一个全新的上下文,但在服务端,应用启动后就一直运行着,处理每个用户请求的都是在同一个应用上下文中。为了不串数据,需要为每次 SSR 请求,创建全新的 app, store, router。

我们主要关注最后一部分asyncData部分

首先会根据上下文环境中的url调用 将匹配的component返回

接着遍历每个component,根据component的asyncData配置,执行 promisify()来promise化asyncData方法并将上下文对象赋给asyncData方法

promisify()方法接受两个参数:第一个组件中配置的asyncData()方法;第二个是挂载到新vue实例上的上下文对象

执行后通过方法将得到的数据同步一份给页面中定义的data,asyncData只是在首屏的时候调用一次,后续交互还是交给client处理

window和document对象的使用

在开发nuxt项目的时候,我们难免会使用到window或者document来获取dom元素。如果直接在文件中使用就会报错。这是因为window或者document是浏览器端的东西服务端并没有。
解决方法:我们只需要在使用的地方通过process.browser/process.server来判断

if (process.browser) {
   // 浏览器中运行代码
}

nuxtServerInit

如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。

举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}

性能优化

keep-alive

nuxt1.2.0 上添加了这个功能的。在layouts/default.vue:

<template>
  <Nuxt keep-alive/>
</template>

服务端渲染合理应用

应用到的特性主要包括asyncData异步获取数据、mounted不支持服务端渲染、client-only组件不在服务端渲染中呈现;

通过相关特性做到API数据和页面结构合理拆分,首屏所需数据和结构通过服务端获取并渲染,非首屏数据和结构通过客户端获取并渲染。

<template>
  <div>
    <!-- 顶部banner -->
    <banner :banner="banner" />
    <!-- 非首屏所需结构,通过client-only组件达到不在服务端渲染目的-->
    <client-only>
      <!-- 列表 -->
      <prod-list :listData="listData"/>
    </client-only>
  </div>
</template>

组件缓存

将渲染后的组件DOM结构存入缓存,定时刷新,有效期通过缓存获取组件DOM结构,减小生成DOM结构所需时间;

适用于渲染后结构不变或只有几种变换、并不影响上下文的组件。

nuxt.config.js配置项修改

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: new LRUCache({
        max: 1000, // 缓存队列长度
        maxAge: 1000 * 60 // 缓存1分钟
      })
    }
  }
}

需要做缓存的 vue 组件, 需增加name以及serverCacheKey字段,以确定缓存的唯一键值。

export default {
  name: 'componentName',
  props: ['item'],
  serverCacheKey: props => props.item.id
}
  • 如果组件依赖于很多的全局状态,或者状态取值非常多,缓存会因频繁被设置而导致溢出,这样的组件做缓存就没有多大意义了;

  • 组件缓存,只是缓存了dom结构,如created等钩子中的代码逻辑并不会被缓存,如果其中逻辑会影响上下边变更,是不会再执行的,此种组件也不适合缓存。

页面整体缓存

当整个页面与用户数据无关,依赖的数据基本不变的情况下,可以对整个页面做缓存,减小页面获取时间;

页面整体缓存前提是在使用Nuxt.js脚手架工具create-nuxt-app初始化项目时,必须选择集成服务器框架,如expresskoa,只有这样才具有服务端中间件扩展的功能。

页面缓存

对于使用nuxt框架而言,用脚手架初始化完,框架对于vue服务端渲染的res.end()函数做了高度封装,
服务端中间件middleware/page-cache.js

import LRUCache from "lru-cache";
const cache = new LRUCache({
  maxAge: 1000 * 60 * 2, // 有效期2分钟
  max: 100 // 最大缓存数量
});

export default function(req, res, next) {
  // 本地开发环境不做页面缓存
  if (process.env.NODE_ENV !== "development") {
    try {
      const cacheKey = req.url;
      const cacheData = cache.get(cacheKey);
      if (cacheData) {
        return res.end(cacheData, "utf8");
      }
      const originalEnd = res.end;
      res.end = function(data) {
        cache.set(cacheKey, data);
        originalEnd.call(res, ...arguments);
        console.log(data);
      };
    } catch (error) {
      console.log(`page-cache-middleware: ${error}`);
      next();
    }
  }
  next();
}

nuxt.config.js配置项修改,引入服务端中间件

serverMiddleware:[
  { path: '/app', handler: '~/middleware/page-cache' }
]

服务器端渲染中间件(serverMiddleware) vs 中间件(middleware)。不要将它与客户端或SSR中Vue在每条路由之前调用的routes middleware混淆。serverMiddleware只是在vue-server-renderer之前在服务器端运行,可用于服务器特定的任务,如处理API请求或服务资产。

问题

服务端的 axios 不会自动携带 Cookie

在 axios 封装那里新增一个接口来注入 Cookie,然后靠 router-middleware 从 context.headers.cookie 获取并注入

资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant