nuxt笔记

160 阅读11分钟

官网:www.nuxtjs.cn/guide/insta…

参考:juejin.cn/post/684490…

基础应用及配置

context

context 是从 Nuxt 额外提供的对象,在"asyncData"、"plugins"、"middlewares"、"modules" 和 "store/nuxtServerInit" 等特殊的 Nuxt 生命周期区域中都会使用到 context。

所以,想要使用 Nuxt.js,我们必须要熟知该对象的有哪些可用属性。

context 官方文档描述戳这里 www.nuxtjs.cn/api/context

下面我列举几个在实际应用中比较重要且常用的属性:

app

appcontext 中最重要的属性,就像我们 Vue 中的 this,全局方法和属性都会挂载到它里面。因为服务端渲染的特殊性,很多Nuxt提供的生命周期都是运行在服务端,也就是说它们会先于 Vue 实例的创建。因此在这些生命周期中,我们无法通过 this 去获取实例上的方法和属性。使用 app 可以来弥补这点,一般我们会把全局的方法同时注入到 thisapp 中,在服务端的生命周期中使用 app 去访问该方法,而在客户端中使用 this,保证方法的共用。

举个例子:

假设 $axios 已被同时注入,一般主要数据通过 asyncData (该生命周期发起请求,将获取到的数据交给服务端拼接成html返回) 去提前请求做服务端渲染,而次要数据通过客户端的 mounted 去请求。

export default {
  async asyncData({ app }) {
    // 列表数据
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    return {
      list
    }
  },
  data() {
    return {
      list: [],
      categories: []
    }
  },
  async mounted() {
    // 分类
    let res = await this.$axios.getCategories()
    if (res.s  === 1) {
      this.categories = res.d
    }
  }
}
复制代码

store

storeVuex.Store 实例,在运行时 Nuxt.js 会尝试找到是应用根目录下的 store 目录,如果该目录存在,它会将模块文件加到构建配置中。

所以我们只需要在根目录的 store 创建模块js文件,即可使用。

/store/index.js :

export const state = () => ({
  list: []
})

export const mutations = {
  updateList(state, payload){
    state.list = payload
  }
}
复制代码

而且 Nuxt.js 会很贴心的帮我们将 store 同时注入,最后我们可以在组件这样使用::

export default {
  async asyncData({ app, store }) {
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    // 服务端使用
    store.commit('updateList', list)
    return {
      list
    }
  },
  methods: {
    updateList(list) {
      // 客户端使用,当然你也可以使用辅助函数 mapMutations 来完成
      this.$store.commit('updateList', list)
    }
  }
}
复制代码

为了明白 store 注入的过程,我翻阅 .nuxt/index.js 源码(.nuxt 目录是 Nuxt.js 在构建运行时自动生成的),大概知道了流程。首先在 .nuxt/store.js 中,对 store 模块文件做出一系列的处理,并暴露 createStore 方法。然后在 .nuxt/index.js 中,createApp方法会对其同时注入:

import { createStore } from './store.js'

async function createApp (ssrContext) {
  const store = createStore(ssrContext)
  // ...
  // here we inject the router and store to all child components,
  // making them available everywhere as `this.$router` and `this.$store`.
  // 注入到this
  const app = {
    store
    // ...
  }
  // ...
  // Set context to app.context
  // 注入到context
  await setContext(app, {
    store
    // ...
  })
  // ...
  return {
    store,
    app,
    router
  }
}
复制代码

除此之外,我还发现 Nuxt.js 会通过 inject 方法为其挂载上 pluginplugin 是挂载全局方法的主要途径,后面会讲到,不知道可以先忽略),也就是说在 store 里,我们可以通过 this 访问到全局方法:

export const mutations = {
  updateList(state, payload){
    console.log(this.$axios)
    state.list = payload
  }
}
复制代码

params、query

paramsquery 分别是 route.paramsroute.query 的别名。它们都带有路由参数的对象,使用方法也很简单。这个没什么好说的,用就完事了。

export default {
  async asyncData({ app, params }) {
    let list = await app.$axios.getIndexList({
      id: params.id,
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    return {
      list
    }
  }
}
复制代码

redirect

该方法重定向用户请求到另一个路由,通常会用在权限验证。用法:redirect(params)params 参数包含 status(状态码,默认为302)、path(路由路径)、query(参数),其中 statusquery 是可选的。当然如果你只是单纯的重定向路由,可以传入路径字符串,就像 redirect('/index')

举个例子:

假设我们现在有个路由中间件,用于验证登录身份,逻辑是身份没过期则不做任何事情,若身份过期重定向到登录页。

export default function ({ redirect }) {
  // ...
  if (!token) {
    redirect({
      path: '/login',
      query: {
        isExpires: 1
      }
    })
  }
}
复制代码

error

该方法跳转到错误页。用法:error(params)params 参数应该包含 statusCodemessage 字段。在实际场景中,总有一些不按常理的操作,页面因此无法展示真正想要的效果,使用该方法进行错误提示还是有必要的。

举个例子:

标签详情页面请求数据依赖于 query.name,当 query.name 不存在时,请求无法返回可用的数据,此时跳转到错误页

export default {
  async asyncData({ app, query, error }) {
    const tagInfo = await app.$api.getTagDetail({
      tagName: encodeURIComponent(query.name)
    }).then(res => {
      if (res.s === 1) {
        return res.d
      } else {
        error({
          statusCode: 404,
          message: '标签不存在'
        })
        return
      }
    })
    return {
      tagInfo
    }
  }
}

Nuxt常用页面生命周期

asyncData

你可能想要在服务器端获取并渲染数据。Nuxt.js添加了asyncData方法使得你能够在渲染组件之前异步获取数据。

asyncData 是最常用最重要的生命周期,同时也是服务端渲染的关键点。该生命周期只限于页面组件调用,第一个参数为 context。它调用的时机在组件初始化之前,运作在服务端环境。所以在 asyncData 生命周期中,我们无法通过 this 引用当前的 Vue 实例,也没有 window 对象和 document 对象,这些是我们需要注意的。

一般在 asyncData 会对主要页面数据进行预先请求,获取到的数据会交由服务端拼接成 html 返回前端渲染,以此提高首屏加载速度和进行 seo 优化。

看下图,在谷歌调试工具中,看不到主要数据接口发起请求,只有返回的 html 文档,证明数据在服务端被渲染。

最后,需要将接口获取到的数据返回:

export default {
  async asyncData({ app }) {
    let list = await app.$axios.getIndexList({
      pageNum: 1,
      pageSize: 20
    }).then(res => res.s === 1 ? res.d : [])
    // 返回数据
    return {
      list
    }
  },
  data() {
    return {
      list: []
    }
  }
}
复制代码

值得一提的是,asyncData 只在首屏被执行,其它时候相当于 createdmounted 在客户端渲染页面。

什么意思呢?举个例子:

现在有两个页面,分别是首页和详情页,它们都有设置 asyncData。进入首页时,asyncData 运行在服务端。渲染完成后,点击文章进入详情页,此时详情页的 asyncData 并不会运行在服务端,而是在客户端发起请求获取数据渲染,因为详情页已经不是首屏。当我们刷新详情页,这时候详情页的 asyncData 才会运行在服务端。所以,不要走进这个误区(诶,不是说服务端渲染吗,怎么还会发起请求?)。

fetch

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

查看官方的说明,可以得知该生命周期用于填充 Vuex 状态树,与 asyncData 同样,它在组件初始化前调用,第一个参数为 context

为了让获取过程可以异步,你需要返回一个 PromiseNuxt.js 会等这个 promise 完成后再渲染组件。

export default {
  fetch ({ store, params }) {
    return axios.get('http://my-api/stars')
    .then((res) => {
      store.commit('setStars', res.data)
    })
  }
}
复制代码

你也可以使用 async 或 await 的模式简化代码如下:

export default {
  async fetch ({ store, params }) {
    let { data } = await axios.get('http://my-api/stars')
    store.commit('setStars', data)
  }
}
复制代码

但这并不是说我们只能在 fetch 中填充状态树,在 asyncData 中同样可以。

validate

Nuxt.js 可以让你在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性。

在验证路由参数合法性时,它能够帮助我们,第一个参数为 context。与上面有点不同的是,我们能够访问实例上的方法 this.methods.xxx

打印 this 如下:

生命周期可以返回一个 Boolean,为真则进入路由,为假则停止渲染当前页面并显示错误页面:

export default {
  validate({ params, query }) {
    return this.methods.validateParam(params.type)
  },
  methods: {
    validateParam(type){
      let typeWhiteList = ['backend', 'frontend', 'android']
      return typeWhiteList.includes(type)
    }
  }
}
复制代码

或者返回一个Promise:

export default {
  validate({ params, query, store }) {
    return new Promise((resolve) => setTimeout(() => resolve()))
  }
}
复制代码

还可以在验证函数执行期间抛出预期或意外错误:

export default {
  async validate ({ params, store }) {
    // 使用自定义消息触发内部服务器500错误
    throw new Error('Under Construction!')
  }
}
复制代码

校验参数: validate

-   在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性
  • pages/index.vue
<script>
  export default {
    // 同步模式
    validate({ params, query }) {
      return true // 如果参数有效
      return false // 参数无效,Nuxt.js 停止渲染当前页面并显示错误页面
    }
    // 异步模式
    async validate({ params, query, store }) {
      // await operations
      return true // 如果参数有效
      // return false // 将停止Nuxt.js呈现页面并显示错误页面

      // return为true时会继续访问该页面,return为false则会跳转到错误页面。
    }
  }
</script>

watchQuery

监听参数字符串更改并在更改时执行组件方法 (asyncData, fetch, validate, layout, ...)

watchQuery 可设置 BooleanArray (默认: [])。使用 watchQuery 属性可以监听参数字符串的更改。 如果定义的字符串发生变化,将调用所有组件方法(asyncData, fetch, validate, layout, ...)。 为了提高性能,默认情况下禁用。

nuxt-juejin-project 项目的搜索页中,我也用到了这个配置:

<template>
  <div class="search-container">
    <div class="list__header">
      <ul class="list__types">
        <li v-for="item in types" :key="item.title" @click="search({type: item.type})">{{ item.title }}</li>
      </ul>
      <ul class="list__periods">
        <li v-for="item in periods" :key="item.title" @click="search({period: item.period})">{{ item.title }}</li>
      </ul>
    </div>
  </div>
</template>
复制代码
export default {
  async asyncData({ app, query }) {
    let res = await app.$api.searchList({
      after: 0,
      first: 20,
      type: query.type ? query.type.toUpperCase() : 'ALL',
      keyword: query.keyword,
      period: query.period ? query.period.toUpperCase() : 'ALL'
    }).then(res => res.s == 1 ? res.d : {})
    return {
      pageInfo: res.pageInfo || {},
      searchList: res.edges || []
    }
  },
  watchQuery: ['keyword', 'type', 'period'],
  methods: {
    search(item) {
      // 更新路由参数,触发 watchQuery,执行 asyncData 重新获取数据
      this.$router.push({
        name: 'search',
        query: {
          type: item.type || this.type,
          keyword: this.keyword,
          period: item.period || this.period
        }
      })
    }
  }
}
复制代码

使用 watchQuery有点好处就是,当我们使用浏览器后退按钮或前进按钮时,页面数据会刷新,因为参数字符串发生了变化。

head

Nuxt.js 使用了 vue-meta 更新应用的 头部标签(Head) 和 html 属性。

使用 head 方法设置当前页面的头部标签,该方法里能通过 this 获取组件的数据。除了好看以外,正确的设置 meta 标签,还能有利于页面被搜索引擎查找,进行 seo 优化。一般都会设置 description(简介) 和 keyword(关键词)。

title:

meta:

export default {
  head () {
    return {
      title: this.articInfo.title,
      meta: [
        { hid: 'description', name: 'description', content: this.articInfo.content }
      ]
    }
  }
}
复制代码

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

nuxt.config.js 中,我们还可以设置全局的 head:

module.exports = {
  head: {
    title: '掘金',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover' },
      { name: 'referrer', content: 'never'},
      { hid: 'keywords', name: 'keywords', content: '掘金,稀土,Vue.js,微信小程序,Kotlin,RxJava,React Native,Wireshark,敏捷开发,Bootstrap,OKHttp,正则表达式,WebGL,Webpack,Docker,MVVM'},
      { hid: 'description', name: 'description', content: '掘金是一个帮助开发者成长的社区,是给开发者用的 Hacker News,给设计师用的 Designer News,和给产品经理用的 Medium。掘金的技术文章由稀土上聚集的技术大牛和极客共同编辑为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。用户每天都可以在这里找到技术世界的头条内容。与此同时,掘金内还有沸点、掘金翻译计划、线下活动、专栏文章等内容。即使你是 GitHub、StackOverflow、开源中国的用户,我们相信你也可以在这里有所收获。'}
    ],
  }
}
复制代码

补充

下面是这些生命周期的调用顺序,某些时候可能会对我们有帮助。

validate  =>  asyncData  =>  fetch  =>  head

配置启动端口

以下两者都可以配置启动端口,但我个人更喜欢第一种在 nuxt.config.js 配置,这比较符合正常的逻辑。

第一种

nuxt.config.js :

module.exports = {
  server: {
    port: 8000,
    host: '127.0.0.1'
  }
}
复制代码

第二种

package.json :

"config": {
  "nuxt": {
    "port": "8000",
    "host": "127.0.0.1"
  }
},

加载外部资源

全局配置

nuxt.config.js :

module.exports = {
  head: {
    link: [
      { rel: 'stylesheet', href: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-light.min.css' },
    ],
    script: [
      { src: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js' }
    ]
  }
}
复制代码

组件配置

组件内可在 head 配置,head 可以接受 objectfunction。官方例子使用的是 object 类型,使用 function 类型同样生效。

export default {
  head () {
    return {
      link: [
        { rel: 'stylesheet', href: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/styles/atom-one-light.min.css' },
      ],
      script: [
        { src: '//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.18.1/build/highlight.min.js' }
      ]
    }
  }
}
复制代码

环境变量

nuxt.config.js提供env选项进行配置环境变量。但此前我尝试过根目录创建 .env 文件管理环境变量,发现是无效的。

创建环境变量

nuxt.config.js :

module.exports = {
  env: {
    baseUrl: process.env.NODE_ENV === 'production' ? 'http://test.com' : 'http://127.0.0.1:8000'
  },
}
复制代码

以上配置我们创建了一个 baseUrl 环境变量,通过 process.env.NODE_ENV 判断环境来匹配对应的地址

使用环境变量

我们可以通过以下两种方式来使用 baseUrl 变量:

  1. 通过 process.env.baseUrl
  2. 通过 context.env.baseUrl

举个例子, 我们可以利用它来配置 axios 的自定义实例。

/plugins/axios.js:

export default function (context) {
	$axios.defaults.baseURL = process.env.baseUrl
	// 或者 $axios.defaults.baseURL = context.env.baseUrl
	$axios.defaults.timeout = 30000
	$axios.interceptors.request.use(config => {
		return config
	})
	$axios.interceptors.response.use(response => {
		return response.data
	})
}

中间件运行: middleware

-   中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。每一个中间件应放置在 middleware/ 目录  
    文件名的名称将成为中间件名称 (middleware/auth.js将成为auth 中间件),这里定义一下这个auth 中间件
-   全局需要在 nuxt.config.js 中配置 router: { middleware: 'auth' }
export default ({ store, route, redirect, params, query, req, res }) => { 
// context 服务端上下文 
// 全局守卫业务 console.log('middleware nuxt.config.js outside') 
// store状态树信息 
// route 一条目标路由信息 
//redirect 路由的强制跳转 
// params query 校验参数的合理性
// console.log('全局守卫前置守卫') 
// redirect('/login') }
  • 页面层级的中间件使用/定义
<script> export default { 
// middleware: 'auth' // 页面层级中间件定义 
middleware({ store, route, redirect, params, query }) { 
console.log('middleware layouts inside') 
// store状态树信息 // route 一条目标路由信息 
//redirect 路由的强制跳转 
// params query 校验参数的合理性 
// console.log('layouts守卫前置守卫') 
// redirect('/reg') 
} } 
</script>

中间件执行顺序: nuxt.config.js(配置文件级别) > layouts(布局文件级别) > pages(页面级别)

插件 plugins

Nuxt.js 允许您在运行 Vue.js 应用程序之前执行 js 插件。这在您需要使用自己的库或第三方模块时特别有用。

plugins 作为全局注入的主要途径,关于一些使用的方式是必须要掌握的。有时你希望在整个应用程序中使用某个函数或属性值,此时,你需要将它们注入到 Vue 实例(客户端), context (服务器端)甚至 store(Vuex) 。

需要注意的是,在任何 Vue 组件的生命周期内, 只有 beforeCreate 和 created 这两个方法会在 客户端和服务端被调用。其他生命周期函数仅在客户端被调用。

plugins 属性配置的所有插件会在 Nuxt.js 应用初始化之前被加载导入

每次你需要使用 Vue.use() 时,你需要在 plugins/ 目录下创建相应的插件文件,并在 nuxt.config.js 中的 plugins 配置项中配置插件的路径。

plugin 函数参数

plugin 一般向外暴露一个函数,该函数接收两个参数分别是 contextinject

context: 上下文对象,该对象存储很多有用的属性。比如常用的 app 属性,包含所有插件的 Vue 根实例。例如:在使用 axios 的时候,你想获取 $axios 可以直接通过 context.app.$axios 来获取。

inject: 该方法可以将 plugin 同时注入到 contextVue 实例, Vuex 中。

例如:

export default function (context, inject) {}

注入 Vue 实例
定义

plugins/vue-inject.js :

import Vue from 'vue'

Vue.prototype.$myInjectedFunction = string => console.log('This is an example', string)
使用

nuxt.config.js :

export default {
  plugins: ['~/plugins/vue-inject.js']
}

这样在所有 Vue 组件中都可以使用该函数

export default {
  mounted() {
      this.$myInjectedFunction('test')
  }
}

注入 context

context 注入方式和在其它 vue 应用程序中注入类似。

定义

plugins/ctx-inject.js :

export default ({ app }) => {
  app.myInjectedFunction = string => console.log('Okay, another function', string)
}

使用

nuxt.config.js :

export default {
  plugins: ['~/plugins/ctx-inject.js']
}

现在,只要你获得 context ,你就可以使用该函数(例如在 asyncDatafetch 中)

export default {
  asyncData(context) {
    context.app.myInjectedFunction('ctx!')
  }
}

同时注入

如果需要同时在 contextVue 实例,甚至 Vuex 中同时注入,可以使用 inject 方法,它是 plugin 导出函数的第二个参数。系统会默认将 $ 作为方法名的前缀。

定义

plugins/combined-inject.js :

export default ({ app }, inject) => {
  inject('myInjectedFunction', string => console.log('That was easy!', string))
}

使用

nuxt.config.js :

export default {
  plugins: ['~/plugins/combined-inject.js']
}

现在你就可以在 context ,或者 Vue 实例中的 this ,或者 Vuexactions / mutations 方法中的 this 来调用 myInjectedFunction 方法

export default {
  mounted() {
    this.$myInjectedFunction('works in mounted')
  },
  asyncData(context) {
    context.app.$myInjectedFunction('works with context')
  }
}

store/index.js :

export const state = () => ({
  someValue: ''
})

export const mutations = {
  changeSomeValue(state, newValue) {
    this.$myInjectedFunction('accessible in mutations')
    state.someValue = newValue
  }
}

export const actions = {
  setSomeValueToWhatever({ commit }) {
    this.$myInjectedFunction('accessible in actions')
    const newValue = 'whatever'
    commit('changeSomeValue', newValue)
  }
}

plugin相互调用

plugin 依赖于其他的 plugin 调用时,我们可以访问 context 来获取,前提是 plugin 需要使用 context 注入。

举个例子:现在已存在 request 请求的 plugin ,有另一个 plugin 需要调用 request

plugins/request.js :

export default ({ app: { $axios } }, inject) => {
  inject('request', {
    get (url, params) {
      return $axios({
        method: 'get',
        url,
        params
      })
    }
  })
}

plugins/api.js

export default ({ app: { $request } }, inject) => {
  inject('api', {
    getIndexList(params) {
      return $request.get('/list/indexList', params)
    }
  })
}

值得一提的是,在注入 plugin 时要注意顺序,就上面的例子来看, request 的注入顺序要在 api 之前

module.exports = {
  plugins: [
    './plugins/axios.js',
    './plugins/request.js',
    './plugins/api.js',
  ]
}

异步数据处理: asyncData() & fetch()

-   asyncData() 在服务器端获取并渲染数据。可以在渲染组件之前异步获取数据
-   fetch() 用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据
<script>
  export default {
   asyncData({store, params}) {

   }

   fetch(store, params) {

   }
  }
</script>

服务端和客户端共有的生命周期

  • beforeCreate与 created异步数据

    • 这两个方法在客户端或服务端都会被执行
<script>
  export default {
    beforeCreate() {
		console.log("beforeCreate")
		},
    created() {
		console.log("created")
		}
  }
</script>

Nuxt路由

  • 约定式路由

    • Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置
    • 要在页面之间使用路由, Nuxt.js 建议我们使用 <nuxt-link> 标签
    • 在约定式路由中,会根据 page 目录下的层级关系自动生成构建出路由,并且使用 index.vue 文件来作为某个目录下的主页面
  • 案例:

    • 这里我们在 page 目录文件下新建 list.vue 文件,来进行路由的测试
<template>
  <div>
    <!-- 
      跳转: 声明式 路由跳转 【此时的路由我们称为是一级路由跳转】
    -->
    <!-- 
      此时携带的参数:
        name: 指向的是路由(文件夹或文件名),而路由死活区分大小写的,所以to后面区分大小写,建议文件夹都写成小写的
        注意:
          使用 query 传递参数会拼接在路由后面显示在地址栏中
          使用 params 纯涤的参数不会拼接在路由后面,地址栏上看不到参数
     -->
    <nuxt-link :to="{name: 'list', query: {id: 1}, params: {id: 1}}">跳转列表页面</nuxt-link>

    <!-- 编程式路由导航 -->
    <button @click="toGoods">跳转到商品页</button>

    <!-- 展示区: 类似于 router-view -->
    <Nuxt />
  </div>
</template>

<script>
export default {
  methods: {
    toGoods() {
      /* 编程式路由导航, 携带 query 参数 */
      this.$router.push({
        name: 'goods',
        query: {id: 1}
      })
    }
  }
}
</script>

<style scoped>

</style>
  • 在 list.vue 页面中写以下的代码

路由校验
Nuxt.js可以让你在动态路由对应的页面组件中配置一个 validate 方法
用于校验动态路由参数的有效性,该函数有一个布尔类型的返回值:
如果返回true则表示校验通过,如果返回false则表示校验未通过

<template>
  <div class="list">
    <h3>列表页面</h3>
  </div>
</template>

<script>
export default {
  created() {
    console.log(this.$route)
  },
  /* 
    路由校验:
      nuxt中使用 validate 方法进行路由参数校验,这个方法必须返回一个布尔值 
      为true表示校验通过,为false表示校验失败
    注意 validate不能写到 methods属性中
  */
   validate({params}) {
    // console.log(obj);
    // return true
    return /^\d+$/.test(params.id)
  }
}
</script>

<style>

</style>
  • 动态路由

    • 在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录
  • 嵌套路由

    • 添加一个Vue文件,作为父组件
    • 添加一个与父组件同名的文件夹来存放子视图组件
    • 在父文件中,添加组件,用于展示匹配到的子视图

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

 别忘了在父组件(.vue文件) 内增加 <nuxt-child/> 用于显示子视图内容。

image.png

image.png

  • 路由守卫

    • 前置路由守卫: 【依赖于中间件: middleware 或者插件】
    • 后置路由守卫: 【使用 Vue 的 beforeRouteleave 钩子函数】
  /* 
    middleware
      需要在 nuxt.config.js 中配置 router: { middleware: 'auth' }
      然后根目录新建 middleware 文件夹---> auth.js
    
    这种的我们称之为是中间件的路由守卫, 我们可以获取到 { store, route, redirect, params, query, req, res }
      这些个对象属性,当我们需要进行数据拦截之后可以通过 redirect 对页面进行强制跳转
      例如: 一个登录系统, 在没有登录之前是无法访问的,那么这时 就可以进行将其拦截, 给其重定向到登录页
  */
 export default ({ store, route, redirect, params, query, req, res }) => {
  // context 服务端上下文
  // 全局守卫业务
  
  // store状态树信息
  // route 一条目标路由信息
  //redirect 路由的强制跳转
  // params query 校验参数的合理性
  // console.log('全局守卫前置守卫')
  // redirect('/login')
}
  /* 
    插件 
      插件的路由守卫,这里我们需要先定义一个插件,在plugins目录下新建一个route.js文件作为一个插件
      之后将该插件在nuxt.config.js文件当中进行配置
    
    在插件当中我们可以获取到app对象,再通过app对象的beforeEach和afterEach进行设置前置和后置守卫
    分别对应的是进入页面于离开页面要执行的方法和操作
  */
 /* 插件全局守卫 */
export default ({ app, redirect, params, query, store }) => {
  console.log('插件')
  // app == vue实例
  // redirect 跳转函数
  app.router.beforeEach((to, from, next) => {
    // 全局前置守卫 -- 插件
    // console.log('插件配置,', to)
    // if (to.name === 'login' || to.name === 'reg') {
    //   next()
    // } else {
    //   redirect({ name: 'login' })
    // }
    next()
  })

  app.router.afterEach((to, from) => {
    console.log('插件全局后置守卫')
  })
}
  /* nuxt.config.js */
  plugins: [
    '~/plugins/router',
  ],
  <!-- 
  而对于页面进行路由守卫的配置,我们同样的还是采用钩子进行定义
    使用 beforeRouteLeave钩子当离开当前路由触发该方法(路由守卫--组件后置)
  -->
  <script>
  export default {
    beforeRouteLeave(to,from,next){
      next(window.confirm("是否离开"))
    }
  }
</script>
  • router.js 导航守卫

    • 下载 @nuxtjs/router 这个插件: npm install @nuxtjs/router -S
    • 下载ok以后在 next.config.js 的 modules 配置项中进行配置
      modules: [
      '@nuxtjs/router'
      ]
    • 把 router 文件放入 nuxt项目根目录 【router.js】-- 注意: 使用此插件不支持路由懒加载
    • 修改配置返回的内容
      export function createRouter() {
      return new VueRouter({
      mode: 'history',
      routes
      })
      }

Nuxt 配置

  • nuxt.config.js 配置 head
  <!-- 组件内部 -->
<script>
  export default {
    head() {
      return {
        title: '标题',
        meta: [
          { hid: 'description', name: 'description', content: '此处是网站描述' },
          { name: 'keywords', name: 'keywords' content: '此处是网站关键词' }
        ]
      }
    }
  }
</script>
  • nuxt.config.js 配置 css

在开发多页项目时,都会定义一个全局的 CSS来初始化我们的页面渲染
网上也有非常出名的开源css文件normailze.css
需要在 nuxt.config.js里进行操作

  css:['~assets/css/normailze.css'],
  • 设置好后,在终端输入npm run dev 然后你会发现样式以及起效了

  • nuxt.config.js 配置 plugins

    • 此配置就是用于配置全局的插件:例如 ElementUI

数据交互

  • Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据

  • asyncData方法会在组件(限于页面组件)每次加载之前被调用

    • 它可以在服务端或路由更新之前被调用。
    • 在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象
    • 可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件
    • 注意: asyncData方法是在组件初始化前被调用的,所以在方法内是没有办法通过 this 来引用组件的实例对象
  • 既然要进行数据交互,那么必然少不了axios来进行发送请求,同时我们将nuxt的代理的依赖加入进来

    • npm i @nuxtjs/axios @nuxtjs/proxy --save
  • 可以将axios直接丢到 nuxt.config.js 中的 modules 配置中,后续我们就可以直接再项目当中通过$axios进行获取axios对象
    modules: [
    '@nuxtjs/axios'
    ],

  • 在前面我们有说过nuxt的目录,其中static是用来存放相对应的静态文件的,这里我们在static下加上一个data/data.json文件用来模拟axios返回的数据

  {
    "title": "next数据"
  }
  • 然后我们可以在首页中进行数据的获取
<template>
  <div>
    <h2>首页</h2>
    <span>{{title}}</span>
  </div>
</template>

<script>
export default {
  /*  
    这里直接使用异步的方式来进行对asyncData方法进行测试,获取前面加入的json文件的数据
      并且可以直接进行return返回给到客户端直接渲染
    
    这里是获取同域的资源数据,但是当我们发送一个请求访问的是跨域的资源的时候,可以看到会报一个跨域的错误
  */
  async asyncData({ $axios }) {
    let res = await $axios({ url: '/data/list.json' })
    console.log('读取到了静态资源',res.data)
    return {
      title: res.data.title
    }
  }
}
</script>
  • 解决跨域问题

    • 这个时候我们需要在配置文件当中加上允许跨域和代理的配置
  axios: {
    proxy: true,
  },

  proxy: {
    '/api': {
      target: "http://localhost:8888",
      chageOrigin: true,
      pathRewrite: {}
    }
  },
  • 拦截配置与携带token

    • 我们可以对axios异步请求进行请求、响应、error拦截。这里使用一个插件来对axios进行统一管理
    • 添加一个axios.js作为一个插件,将插件加入到配置文件当中
  	plugins: [
		{
			src: '~/plugins/axios',
			ssr: true // 开启服务端渲染
		}
	],
  • 这里在axios.js文件当中获取$axios对象,对其请求(onRequest)、响应(onResponse)、异常(onError)方法进行拦截处理
 export default function({ $axios, redirect, route, store, app:{$cookies} }) {
  // 基本配置
  $axios.defaults.timeout = 6000

  // 请求拦截
  $axios.onRequest(config => {
		console.log("请求拦截")
		config.headers.token = state.user.token
		return config
	})
  // 响应拦截
  $axios.onResponse(res => {
		console.log("响应拦截")
		if (res.data.err === 2 && route.fullPath !== '/login') {
      redirect('/login?path=' + route.fullPath)
    }
    return res
	})

  // 错误拦截
	$axios.onError(error => {
		console.log("请求异常")
		return error
	})
 } 

自定义loadding

  • nuxt支持自定义的loading加载动效。在配置文件当中我们可以使用nuxt自带的简单的loadding加载

    loading: {color: "#339",height: "3px"},
    
  • 同样我们也可以进行自定义的loadding加载,在 nuxt.config.js 配置文件中添加

    loading:'~/components/loadding.vue'
    
    • 这里对应的loadding自定义加载就简单写了,后续的样式可以导入elementui来进行美化
  <template>
	<div>
		<div v-if="loadding">loading...</div>
	</div>
</template>

<script>
	export default {
		data() {
			return {
				loadding: true
			}
		},
		methods: {
			start() {
				this.loadding = true
			},
			finish() {
				this.loadding = false
			}
		}
	}
</script>

Vuex

  • 这里的和 你在学习 Vue中的 Vuex 几乎一致
  • 首先我们在store目录下使用 index.js 来作为默认的主store状态树
// 主模块

//state/index.js
export const state = () => ({
  bNav: false,
  bLoading: false
})

//mutations
export const mutations = {
  M_UPDATE_NAV(state, payload) {
    state.bNav = payload
  },
  M_UPDATE_LOADING(state, payload) {
    state.bLoading = payload
  }
}

// actions
export const actions = {
  nuxtServerInit(store, { app: { $cookies } }) {
    // 初始化东西到store里 token信息
    // console.log('nuxtServerInit', store, context)
    console.log('nuxtServerInit')
    let user = $cookies.get('user') ? $cookies.get('user') : {err:2, msg: '未登录', token: ''}
    store.commit('users/M_UPDATE_USER', user)
  }
}

// getters
export const getters = {
  getNav(state) {
    return state.bNav ? '显示' : '隐藏'
  }
}
  // store/home.js
export const state = () => ({
  err: 1,
  data: {}
})

export const mutations = {
  M_UPDATE_HOME(state, payload) {
    state.err = payload.err
    state.data = payload.data
  }
}

export const actions = {
  A_UPDATE_HOME({ commit, state }, payload) {
    // 异步处理
    commit('M_UPDATE_HOME', { err: 0, data: {title: 'home模块 actions所传递的数据'} })
  }
}
  // store/users.js
  export const state = () => ({
  err: 1,
  msg: '未登录',
  token: '',
  data: {}
})

export const mutations = {
  M_UPDATE_USER(user, payload) {
    state.err = payload.err
    state.msg = payload.msg
    state.data = payload.data
    state.token = payload.token
  }
}

export const actions = {
  A_UPDATE_USER({commit, user}, payload) {
    // 异步业务
    commit('M_UPDATE_USER', payload)
  }
}
  • 而在页面当中进行使用直接使用以下代码进行获取数据、而对于其他的相关vuex操作可查看前面关于vuex的文档

    • console.log("state", this.$store.state)

状态持久化与token校验

  • 这里要使用token我们需要先安装该依赖,用来读写cookie到浏览器当中

    • npm i cookie-universal-nuxt --save
  • 安装之后将该依赖加入到配置文件的modules当中
    modules: [
    '@nuxtjs/axios',
    'cookie-universal-nuxt'
    ],

  • 这里我们已登录为例,发送axios请求进行获取数据,将数据再写道cookies当中

  <template>
  <div>
    <button @click="login">login</button>
  </div>
</template>

<script>
  export default {
    methods: {
      login() {
        this.$axios({
          url: '',
          method: 'post',
          data: {
            username: '',
            password: ''
          }
        }).then(res => {
          this.$cookies.set('user', res.data)
          this.$store.commit('', res.data)
        
          if (!this.$route.query.path || /login|reg/.test(this.$route.query.path)) {
            this.$router.replace('/user')
          }else {
            this.$router.replace(this.$router.query.path)
          } 
        })
      }
    }
  }
</script>

第三方组件库(element-UI)

整合ElementUI

  • npm i element-ui --save 进行安装
  • 然后将插件通过配置文件进行注册
    plugins: [
    {
    src: '~/plugins/element-ui',
    ssr: true
    }
    ],
  • 并且将css样式也在配置文件导入
    css: [
    'element-ui/lib/theme-chalk/index.css'
    ],
import Vue from 'vue'
/整体引入
import ElementuI from 'element-ui'
vue.use(ElementUI)

//按需引入全局使用
import {Button} from 'element-ui'
vue.use(Button)
  • 然后就可以在所有的pages页面使用elementui 的组件了

组件注册管理

先来个最简单的例子,在 plugins 文件夹下创建 vue-global.js 用于管理全局需要使用的组件或方法:

import Vue from 'vue'
import utils from '~/utils'
import myComponent from '~/components/myComponent.vue'

Vue.prototype.$utils = utils

Vue.use(myComponent)
复制代码

nuxt.config.js

module.exports = {
  plugins: [
    '~/plugins/vue-global.js'
  ],
}
复制代码

自定义组件

对于一些自定义全局共用组件,我的做法是将它们放入 /components/common 文件夹统一管理。这样可以使用 require.context 来自动化的引入组件,该方法是由 webpack 提供的,它能够读取文件夹内所有文件。如果你不知道这个方法,真的很强烈你去了解并使用一下,它能大大提高你的编程效率。

定义

/components/myComponentsInstall.js :

export default {
  install(Vue) {
    const components = require.context('~/components/common', false, /.vue$/)
    // components.keys() 获取文件名数组
    components.keys().map(path => {
      // 获取组件文件名
      const fileName = path.replace(/(.*/)*([^.]+).*/ig, "$2")
      // components(path).default 获取 ES6 规范暴露的内容,components(path) 获取 Common.js 规范暴露的内容
      Vue.component(fileName, components(path).default || components(path))
    })
  } 
}
复制代码

使用

/plugins/vue-global.js :

import Vue from 'vue'
import myComponentsInstall from '~/components/myComponentsInstall'

Vue.use(myComponentsInstall)
复制代码

经过上面的操作后,组件已在全局被注册,我们只要按短横线命名使用即可。而且每新建一个组件都无需再去引入,真的是一劳永逸。同样在其他实际应用中,如果 api 文件是按功能分模块,也可以使用这个方法去自动化引入接口文件。

全部引入

/plugins/vue-global.js

import Vue from 'vue'
import elementUI from 'element-ui'

Vue.use(elementUI)
复制代码

nuxt.config.js :

module.exports = {
  css: [
    'element-ui/lib/theme-chalk/index.css'
  ]
}
复制代码

按需引入

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

npm install babel-plugin-component -D
复制代码

nuxt.config.js :

module.exports = {
  build: {
    plugins: [
      [
        "component",
        {
          "libraryName": "element-ui",
          "styleLibraryName": "theme-chalk"
        }
      ]
    ],
  }
}
复制代码

接下来引入我们需要的部分组件,同样创建一个 eleComponentsInstall.js 管理 elementUI 的组件:

/components/eleComponentsInstall.js :

import { Input, Button, Select, Option, Notification, Message } from 'element-ui'

export default {
  install(Vue) {
    Vue.use(Input)
    Vue.use(Select)
    Vue.use(Option)
    Vue.use(Button)
    Vue.prototype.$message = Message
    Vue.prototype.$notify  = Notification
  }
}
复制代码

/plugins/vue-global.js:

import Vue from 'vue'
import eleComponentsInstall from '~/components/eleComponentsInstall'

Vue.use(eleComponentsInstall)
复制代码

mixin混入定义全局方法

  • nuxt.config.js
plugins:[  //配置插件
    '~/plugins/mixins'
]
  • plugins/mixins.js
import Vue from 'vue'
let show = ()=>console.log('全局方法')
Vue.propotype.$show = show  //服务端钩子内部不可以使用,this不会执行vue实例

// 全局锅过滤器
import * as filters from '../assets/js/filter.js'
Object.keys(filters).forEach(key=>Vue.filter(key, filters[key]))
assets/js/filter.js

export function filerzero(n) {
  return n < 10 ? '0'+n : ''+n
}

export const date = time => {
  let d = new Date()
  d.setTime(time)
  let year = d.getFullYear()
  let month = d.getMonth()+1
  let date = d.getDate()
  let hour = d.getHours()
  let min = d.getMinutes()
  let sec = d.getSeconds()
  return `${year}${filerzero(month)}${filerzero(date)}${filerzero(hour)}:${min}:${sec}`
}