最近开始学习 Nuxt 框架,写此博文记录学习中遇到的坑。

Nuxt 默认路由与自定义 API 路由

Nuxt 是一个服务端渲染框架,与普通的前后端分离不同(需要同时开两个端口进行开发),而 Nuxt 只需要开一个服务端的端口。默认是 3000。使用 Nuxt 脚手架建立一个 Express.js 模板,打开 server/index.js,发现其中有一行为 app.use(nuxt.render),这行为 Vue-ssr 全部路由的捕获,在这一行下面的所有应用的路由都无法生效,因为 Express 会从上往下检测路由的可达性,一旦有就停止搜索。所以自定义的 API 路由需要放在这行上面。

 // bind routes
  require('../routes/index')(app)

  // Give nuxt middleware to express
  app.use(nuxt.render)

内置的axios 与 asyncData

使用 axios 获取数据并挂载

Nuxt 中内置了 axios,并挂载在 Vue 实例中的 $axios 上,通过在 nuxt.config.js 添加配置可以增加前缀,代理等。

 axios: {
    prefix: '/api/', // 前缀
    proxy: true // 将通过 proxy 对象进行转发
  },
 proxy: {
    '/api/': 'http://api.example.com',
    '/api2/': 'http://api.another-website.com'
  }

一般的我们可以在 this.$axios 获取到 axios 实例,如果你指定了 prefix 属性,在每次请求地址时会自动加上你指定的前缀。

注意:在asyncData中用 this 取得 Vue 实例,因为这时出于 BeforeCreate,你可以通过接受一个参数来取得

  async asyncData ({ app }) {
    const { data } = await app.$axios.get('/user')
    return { user: data.data }
  },
  data () {
    return {}
  }

解构属性 app ,app中取得 axios 实例。在 async 中返回的对象将直接挂载到 data 上。如果 data 中原先有相同的键,将会被覆盖。

axios 拦截器

Nuxt/axios 同样为我们提供了拦截器,与原生的大同小异。

首先在 plugins 中新建一个 axios.js 的文件用于指定 axios 附加配置。

由于没有 axios 实现,所以我们先要接收 axios 实例。

export default ({ $axios }) => {
  $axios.onRequest((config) => {
    console.log('Making request to ' + config.url)
  })

    $axios.onError((error) => {
      console.log('error.' + error.response + err.response.status)
  })
}

不用的是,请求拦截器用 onRequest 代替,响应拦截器用 onResponse代替,错误拦截器可以用 onError代替。

除此之外,一共提供了5个拦截器。

  onRequest: [Function: bound onRequest],
  onResponse: [Function: bound onResponse],
  onRequestError: [Function: bound onRequestError],
  onResponseError: [Function: bound onResponseError],
  onError: [Function: bound onError],

Nuxt 中的子路由

在 Vue 中,我们可以使用在父组件中引入 <router-view /> 的标签创建一个子路由视图,然后通过 vue-router 来控制 router-view 的显示。

在 Nuxt 中,要实现这样的效果,只需要引入 <nuxt-child />,在 Nuxt 中路由自动生成,文件夹即父路由,文件夹里的即子路由,在外层文件夹中加入一个与路由同名的 Vue 文件即可。

pages
    index // index 文件夹
        child.vue  // index 中的子路由
    index.vue // index 父路由

Vuex 与 Nuxt

在 Nuxt 中使用 Vuex,只需要在 store 文件夹下建立 index.js 即可。

切记要重启环境。

否则会出现 "(error during evaluation)"的错误。

Nuxt 中的 vuex 会根据文件自动分成若干个模块。这里说几个我遇到的问题。

/store/viewport.js
export const state = () => ({
  viewport: null
})

export const actions = {
  updateViewport({ state }) {
    state.viewport = {
      w: window.innerWidth,
      h: window.innerHeight,
      mobile: window.innerWidth <= 568,
      laptop: window.innerWidth <= 768,
      desktop: window.innerWidth <= 1024
    }
  }
}

export default { state, actions }

store/viewport.js 是一个模块。在 Nuxt 中每个模块又被设定为 namespaced: true,直接用...mapActions(['viewport']) 是不行的,需要加上空间名。这可能会导致错误。[vuex] unknown action type

...mapActions({
      updateViewport: 'viewport/updateViewport'
    })

在模块中,state 应返回一个函数,函数返回一个对象。否则会有一个警告。

Nuxt 中默认在开发环境中设定了严格模式,在严格模式下外部不能直接调用 action 去改变 state 的值。

Error: form binding with Vuex - Do not mutate vuex store state outside mutation handlers

我们需要在 store/index.js 下关闭严格模式。

export const strict = false

首次请求渲染页面与 SSR

首次请求的过程总体来说分为以下步骤:

所以注意的是,第一次请求的时候完全是在服务端完成渲染的,在 axios 中根本拿不到 window localStorage 这些对象的,因为在服务器里这些对象根本不存在。

所以我们在配置 axios 的时候,需要关闭 ssr 模式。

nuxt.config.js 中,修改为

 plugins: [
    {
      src: '~/plugins/axios', // axios 配置文件路径
      ssr: false
    }
  ],

首次请求携带中文的路由报错

Request path contains unescaped characters 
Nuxt

原因是在首次加载的时候是服务端渲染页面的,通过 http 模块去请求接口,携带中文的接口请求时会报错,需要转码一下。

来到前端 api 文件,在请求地址前面加上 encodeURI 函数。如

async getWithSlug(category, slug) {
      const { data } = await $axios.get(encodeURI(`posts/${category}/${slug}`))
      return data
}

按需引入 ElementUI

首先建立 plugins/element-ui.js,在上面加你需要引入的组件

import Vue from 'vue'
import { Message } from 'element-ui'
import 'element-ui/lib/theme-chalk/icon.css'
import 'element-ui/lib/theme-chalk/message.css'

Vue.use(Message)

nuxt.config.js 中加入到插件列表。

当然这还不够,因为 webpack 打包的时候还是会全部引入。

我们需要使用 babel 的 babel-plugin-component 组件。

 yarn add -D babel-plugin-component 

然后在 nuxt.config.js 中加入 babel 配置。

如果你没有这个对象,就加一个。但是这个列表一定要注意了,新版 babel 必须要,否则会报错。

babel: {
      plugins: [
        [
          'component',
          {
            libraryName: 'element-ui',
            styleLibraryName: 'theme-chalk'
          }
        ]
      ]
    }

webpack 按需加载 Moment.js 时区

一般的使用 Moment.js 都会引入全部 locale 文件,占了差不多 70% 的大小。但是一般我们只需要一个就完事了。

同样在 webpack 中配置,在 nuxt.config.js 中加入 extend 对象,配置 webpack

加入

config.plugins.push(
        new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/)
      )

优化后:


未待完续。