点亮你的Vue技术栈,万字Nuxt.js实践笔记来了

前言

作为一位 Vuer(vue开发者),如果还不会这个框架,那么你的 Vue 技术栈还没被点亮。

Nuxt.js 是什么

Nuxt.js 官方介绍:

Nuxt.js 是一个基于 Vue.js 的通用应用框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。
Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。

如果你熟悉 Vue.js 的使用,那你很快就可以上手 Nuxt.js。开发体验也和 Vue.js 没太大区别,相当于为 Vue.js 扩展了一些配置。当然你对 Node.js 有基础,那就再好不过了。

Nuxt.js 解决什么问题

现在 Vue.js 大多数用于单页面应用,随着技术的发展,单页面应用已不足以满足需求。并且一些缺点也成为单页面应用的通病,单页面应用在访问时会将所有的文件进行加载,首屏访问需要等待一段时间,也就是常说的白屏,另外一点是总所周知的 SEO 优化问题。

Nuxt.js 的出现正好来解决这些问题,如果你的网站是偏向社区需要搜索引擎提供流量的项目,那就再合适不过了。

我的第一个 Nuxt.js 项目

我在空闲的时间也用 Nuxt.js 仿掘金 web 网站:

nuxt-juejin-project 是一个使用 Nuxt.js 仿写掘金的学习项目,主要使用 :nuxt + koa + vuex + axios + element-ui。该项目所有数据与掘金同步,因为接口都是通过 koa 作为中间层转发。主要页面数据通过服务端渲染完成。

在项目完成后的几天,我将记录的笔记整理一下,并加入一些常用的技术点,最后有了这篇文章,希望能够帮到正在学习的小伙伴。

项目介绍里有部分截图,如果jio得可以,请来个 star~

项目地址:https://github.com/ChanWahFung/nuxt-juejin-project

基础应用与配置

项目的搭建参照官网指引,跑个项目相信难不到你们,这里不赘述了。

‍️跑起来 https://www.nuxtjs.cn/guide/installation

关于项目的配置,我选择的是:

  • 服务端:Koa
  • UI框架:Element UI
  • 测试框架:None
  • Nuxt模式:Universal
  • 使用集成的 Axios
  • 使用 EsLint

context

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

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

context 官方文档描述戳这里 https://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 方法为其挂载上 plugin(plugin 是挂载全局方法的主要途径,后面会讲到,不知道可以先忽略),也就是说在 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!')
  }
}

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" @cli
  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值