Nuxt.js综合案例

864 阅读7分钟

在这个项目下,将使用Nuxt.js来完成github上的开源realworld-starter-kit项目,目的在于提升Nuxt.js的掌握程度,熟悉完整的SSR项目部署流程。

学习前提

  • 有一定的Vue.js的使用经验
  • 有一定的Nuxt.js基础
  • 具有相关的Node.js webpack使用经验

学习收获

  • 掌握使用Nuxt.js开发同构渲染应用
  • 增强Vue.js实践能力
  • 掌握同构渲染应用中常见的功能处理
    • 用户状态管理
    • 页面访问权限处理
    • SEO优化
    • ...
  • 掌握同构渲染应用的发布与部署

案例相关资源

GitHub仓库
在线示例
接口文档
页面模板

项目初始化

mkdir realworld-nuxtjs
npm init -y
npm i nuxt
添加npm scripts:`"dev": "nuxt"`
创建pages目录,配置初始页面

导入页面模板

案例中用到的样式表中,有三个是国外的链接,因此国内的话会非常慢,所以给他改成本地的样式文件。
其中的ionicons可以用cdn来替换,但是要替换成版本一致的2.0.1。
googleapis在国内是可以被支持的,所以可以不用管。最后一个demo.productionready.io/main.css可以下载到本地,存储到static\main.css文件中。至此,样式文件全部准备好了。

布局组件

首先创建Nuxt.js配置文件nuxt.config.js,清空Nuxt.js自动生成的路由表规则,路由表规则全部使用自定义的,以免因为pages下的目录结构影响路由规则。

module.exports = {
    router: {
        // 自定义路由表规则
        extendRoutes (routes, resolve) {
            // 清除Nuxt.js基于pages目录生成的路由表规则
            routes.splice(0)
            routes.push(...[
                {
                    path: '/',
                    component: resolve(__dirname, 'pages/layout')
                }
            ])
        }
    }
}

之后将realworld-starter-kit中的组件静态内容复制到项目中,并将其部分内容转换成Nuxt.js的语法。
导航链接的高亮处理:
原realworld-starter-kit中高亮是用class="active"控制的,但是Nuxt.js中路由组件激活时的默认class是nuxt-link-active,所以在Nuxt配置文件中设置linkActiveClass为active。

module.exports = {
    router: {
        linkActiveClass: 'active'
    }
}

封装请求模块

因为项目中的所有请求API都在https://conduit.productionready.io下,所以封装一层请求的方法,将基础路径封装进去:

// 基于axios封装的请求模块
import axios from 'axios'
const request = axios.create({
    baseURL: 'https://conduit.productionready.io'
})
export default request

新建api/user.js用于存储操作用户数据的api接口,统一管理便于以后的维护。

import request from '@/utils/request'
export const login = (data) => {
    return request({
        method: 'POST',
        data,
        url: '/api/users/login'
    })
}
export const register = (data) => {
    return request({
        method: 'POST',
        data,
        url: '/api/users'
    })
}

多个请求可以使用Promise.all,但是一定记得加await

处理登录逻辑

跨域身份验证(JWT)
在客户端拿到页面之后,进行登录的逻辑操作,会把用户信息存储在state中,但是如果刷新之后,state中的用户信息就会被初始化,所以需要在服务端渲染的时候就初始化state中的信息。因为服务端渲染是根据cookie来判断是否含有用户信息的,所以登录成功之后客户端需要把用户信息存储在cookie中。

const cookieparser = process.server ? require('cookieparser') : undefined
// 在服务端渲染期间运行的都是同一个实例
// 为了防止数据冲突,无比要把state定义成一个函数,返回对应的数据对象
export const state = () => {
    return {
        // 当前登录用户的基本信息
        user: null
    }
}
export const mutations = {
    setUser (state, data) {
        state.user = data
    }
}
export const actions = {
    // nuxyServerInit 是一个特殊的action方法
    // 这个action会在服务端渲染期间自动调用
    // 主要作用是用来初始化容器数据,传递数据给客户端使用
    nuxtServerInit ({commit}, {req}) {
        let user = null
        // 如果请求头中有cookie
        if (req.headers.cookie) {
            // 使用cookieparser转成js对象
            const parsed = cookieparser.parse(req.headers.cookie)
            try {
                user = JSON.parse(parsed.user)
            } catch (err) {

            }
        }
        // 提交mutation 修改state状态
        commit('setUser', user)
    }
}

中间件
在不登陆的情况下,直接请求其他页面的路径也是可以出来的,这个会是一个很大的问题,所以需要使用Nuxt.js中的中间件来处理。
中间件允许定义一个自定义函数运行在一个页面或一组页面渲染之前。
每一个中间件应放置在 middleware/ 目录。文件名的名称将成为中间件名称 (middleware/auth.js将成为 auth 中间件)。中间件接收context为第一个参数,参数中有项目中的各种方法。
中间件执行流程顺序:
1、nuxt.config.js
2、匹配布局
3、匹配页面

// 校验登录信息
// 校验登录信息
export default function ({ store, redirect }) {
    if (!store.state.user) {
        return redirect('/login')
    }
}

watchQuery监听参数字符串更改并在更改时执行组件方法
使用watchQuery监听参数字符串的更改,如果定义的字符串发生变化,将调用所有组件方法(asyncData,fetch,validate,layout...)。为了提高性能,默认情况下禁用。

export default {
	watchQuery: ['page']
}

插件及拦截器

在请求中需要我们携带Token来标识当前用户,我们不可能每个请求都手动加上token,这是就需要拦截器了。
因为所有的请求都是从封装成的request中发送出去的,如下:

// 基于axios封装的请求模块
import axios from 'axios'
const request = axios.create({
    baseURL: 'https://conduit.productionready.io'
})

所以可以直接对request方法进行拦截,在请求发送之前将token写入到请求头中,但是在拦截器中是无法拿到存储在state中的user数据的,所以需要用到插件plugins。
Nuxt.js允许在运行 Vue.js 应用程序之前执行js插件。这在需要使用自己的库或第三方模块时特别有用。需要注意的是,在任何Vue组件的生命周期内,只有beforeCreate和created这两个方法会在客户端和服务端被调用。其他生命周期函数仅在客户端被调用。 在插件中,可以 拿到已经处理过的store中的数据。

// 基于axios封装的请求模块
import axios from 'axios'
// 创建请求对象
export const request = axios.create({
    baseURL: 'https://conduit.productionready.io'
})
// 通过插件机制获取上下文对象
// 插件导出函数只能作为default成员
export default ({store}) => {
    // 请求拦截器
    // 任何请求都要经过请求拦截器
    // 可以在请求拦截器中做一些公共的业务处理
    // 例如统一设置Token
    request.interceptors.request.use(function (config) {
        // 请求经过这里,请求正确的情况(请求还未发出)
        // 在请求中修改Token
        const user = store.state.user
        if (user && user.token) {
            config.headers.Authorization = `Token ${user.token}`
        }
        // 返回config请求对象,如果不返回请求就发不出去了
        return config
    }, function (error) {
        // 如果请求失败,此时请求还没有发出去
        return Promise.reject(error)
    })
}

SEO优化

优化SEO有两部分很重要:页面标题和meta标签相关的内容。
可以使用以下参数配置vue-meta来修改meta标签的信息

{
  keyName: 'head', // 设置 meta 信息的组件对象的字段,vue-meta 会根据这 key 值获取 meta 信息
  attribute: 'n-head', // vue-meta 在监听标签时所添加的属性名
  ssrAttribute: 'n-head-ssr', // 让 vue-meta 获知 meta 信息已完成服务端渲染的属性名
  tagIDKeyName: 'hid' // 让 vue-meta 用来决定是否覆盖还是追加 tag 的属性名
}

Nuxt.js允许在nuxt.config.js里定义应用所需的所有默认head中的标签,在head字段里配置就可以:

head: {
  meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' }
  ],
  link: [
    { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Roboto' }
  ]
}

Nuxt.js使用hid而不是默认值vmid识别元素key
因为无法在组件中拿到外部的标签,所以提供了head方法来设置当前页面的头部标签
head方法里可通过this关键字来获取组件的数据,你可以利用页面组件的数据来设置个性化的meta标签:

<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    data() {
      return {
        title: 'Hello World!'
      }
    },
    head() {
      return {
        title: this.title,
        meta: [
          {
            hid: 'description',
            name: 'description',
            content: 'My custom description'
          }
        ]
      }
    }
  }
</script>

为了避免子组件中的meta标签不能正确覆盖父组件中相同的标签而产生重复的现象,建议利用hid键为meta标签配一个唯一的标识编号。