1

概述

前端领域中,随着Vue,React,Angular等框架的流行,前端工程化、模块化成为了当下主流技术方案。这类框架构建的SPA单页应用具有用户体验好、渲染性能好、可维护性高等有点,但是也存在以下两个方面的问题:

  1. 首屏加载时间长

SPA应用采用客户端渲染,用户需要等待客户端js解析完成之后才能看到页面,这样会导致首屏加载时间变长,用户体验差。

  1. 不利于SEO

由于SPA应用采用客户端渲染,在js未完成解析之前,网站HTML是没有内容的,这样导致搜索引擎爬取站点HTML时获取不到内容。

为了解决这两个问题,业界提出了一种新的解决方案,如下图:
image.png

利用服务端渲染解决首屏加载慢和不利于SEO的缺陷,首屏渲染完成之后,客户端渲染接管页面重新成为单页应用以保证良好的用户体验。这种方式称为现代化服务端渲染方式或者同构渲染。

与传统服务端渲染区别

传统服务端渲染,如JSP可以总结为以下几步:

  1. 客户端发送请求
  2. 服务端根据请求查找模板并获取数据。
  3. 执行渲染
  4. 生成html返回客户端展示

这种传统服务端渲染方式,会存在以下缺点:

  1. 前后端完全耦合,不利于开发维护。
  2. 前端发挥空间小。
  3. 服务端压力大。
  4. 用户体验一般。

而同构渲染只是在首屏渲染的时候和传统服务端类似,都是返回渲染好的html页面,但是,同构渲染中,当客户端展示渲染好的html页面后,客户端渲染会接管页面的控制权,也就是后续的渲染都是由客户端进行的,这样可以保证良好的用户体验。

同构渲染缺点

与单页应用相比,由于首屏渲染采用的是服务端渲染,所以存在以下缺点:

  1. 开发条件有限,开发有限制(服务端只能使用nodejs,而且并不是所有的工具包都能在服务端渲染中使用)。
  2. 涉及构建和部署的要求高(需要部署客户端和服务端,不能再和单页应用一样,部署静态站点即可)。
  3. 更多的服务端负载。

Nuxt.js

Nuxt.js是基于vue技术栈的一种同构应用解决方案,它屏蔽了vuejs构建同构应用的难点。

基本使用

搭建nuxtjs同构应用相对比较简单,通过以下四步即可搭建最简单的同构应用:

  1. 创建项目文件夹。
  2. npm或者yarn安装nuxt。
  3. 添加pages文件夹并在其中添加index.vue文件。
  4. 执行npx nuxt。

基础路由

pages文件夹下存放所有的路由页面vue文件,nuxt会根据pages文件夹自动生成路由。

如pages目录如下:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么nuxt会自动生成如下的路由结构:

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在解析路由结构时,如果遇到以_为前缀的vue文件时,会将其定义为带参数的动态路由。

如下面的文件结构:

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

生成的路由表为:

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'
    }
  ]
}

嵌套路由

在vuejs中,我们可以通过子路由实现路由的多层嵌套,同样在nuxtjs中,可以通过特殊的文件结构实现嵌套路由。

创建嵌套路由,需要添加一个Vue文件,同时添加一个与该文件名称相同的文件夹用于存放子路由组件(在Vue文件中需要添加类似route-view的nuxt-child节点。)

如下面的文件结构:

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

生成的路由表如下:

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'
        }
      ]
    }
  ]
}

自定义路由

nuxtjs不仅可以通过pages文件夹结构自动生成路由表,同样提供了配置文件的方式配置自定义路由。

nuxtjs的配置文件和vue.config.js一样,在项目根目录下创建nuxt.config.js文件,此文件默认导出一个配置对象,配置对象有一个router属性,可以在此属性下配置自定义路由。

export default {
    router: {
        extendRoutes(routes, resolve) {
            // 可以采用此方式清空默认生成的路由
            // routes.splice(0)
            routes.push(...[
                {
                    path: '/',
                    component: resolve(__dirname, 'your.vue'),
                }
            ])
        }
    }
}

模板

nuxtjs是基于html模板生成html文件的,我们可以在项目中修改此模板。

定制模板需要在项目根目录下创建app.html文件,默认的html结构如下:

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

我们可以在head中添加相应自定义的css、js引用。

asyncData

在vuejs项目中,某个组件如果想要从服务端获取数据,一般都会在created声明周期函数中发起请求。在同构应用中,如果想要页面内容能够SEO,那么就不能采用此方式。

在nuxt中为vue组件添加了asyncData方法,此方法会在服务端被执行,执行后的结果会被合并到当前组件的data对象中。

如下例,在服务端获取所有的文章和标签并渲染:

async asyncData() {
    const [articleRes, tagRes] = await Promise.all([
      getAllArticles({
        offest: 0,
        limit: 10,
      }),
      getAllTags(),
    ])

    const { articles, articlesCount } = articleRes.data
    const { tags } = tagRes.data
    return {
      articles,
      articlesCount,
      tags,
    }
  }

需要注意的是,此方法虽然是vue组件的一个方法,但是由于其在服务端被调用,所以方法内部不能通过this对象获取vue实例,如果想要获取实例上的某些信息,可以用上下文对象。

上下文对象

asyncData方法包含一个参数context,context对象上包含路由等常用信息。

route: 当前路由对象
store: store对象
params, query: 路由参数
req, res: 请求,响应对象
redirect: 用于重定向

身份验证

在同构应用中,当用户登录之后,其身份信息需要在客户端和服务端都能够被获取到,因此可以将信息保存在cookie中。

客户端获取信息并存入cookie:

methods: {
    onSubmit() {
        let request = this.isLogin ? login : register
        request(this.user)
            .then(({ data }) => {
                // 获取到用户信息
                const { user } = data
                if (user) {
                    const Cookie = process.client ? require('js-cookie') : undefined
                    // 将获取到的用户信息保存在cookie中
                    Cookie.set('user', user)
                    this.$store.commit('setUser', user)
                }
            })
            .catch((err) => {

            })
    }
}

服务端读取cookie并存入store供客户端使用:

在store的action中可以添加一个名称为nuxtServerInit的异步方法,该方法会在nuxt应用启动的时候被调用。

nuxtServerInit({ commit }, { req }) {
    let user = null
    if (req && req.headers.cookie) {
        const cookieParser = process.server ? require('cookieparser') : undefined;
        const parsed = cookieParser.parse(req.headers.cookie)
        try {
            user = JSON.parse(parsed.user)
        } catch (err) {
        }
    }
    commit('setUser', user)
}

插件

如果想要在vuejs程序运行之前执行某些js操作,如注册第三方组件库,此时可以利用插件。

所有的插件全部放在plugins目录下,如果想要在nuxt应用中使用elementui组件库,可以在plugins文件夹下添加element-ui.js文件:

import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'

Vue.use(Element, { locale })

然后在nuxt.config.js的plugins属性数组中添加相应文件路径:

plugins: [
    '@/plugins/element-ui',
]

之后,就可以在vue组件中正常使用element-ui。


carry
58 声望7 粉丝

学无止境