服务端预渲染之Nuxt(使用篇)


Posted in Javascript onApril 08, 2019

现在大多数开发都是基于 Vue 或者 React 开发的,能够达到快速开发的效果,也有一些不足的地方, Nuxt 能够在服务端做出渲染,然后让搜索引擎在爬取数据的时候能够读到当前页面。

首先要说明一点,我们可以认为我们所编写的 Vue 项目是一个服务端的项目,虽然编写的还是 Vue 项目,但是 Nuxt 是基于服务器环境的。

就简单的说一下 Nuxt 使用。基础只是还是以官方文档为主,如果博客中哪里有问题,欢迎留言指正。

说了这么多,进入正题。

路由

与传统的 Vue 项目不同的是,我们在使用 Vue 的时候需要配置 Vue-Router 信息,在 Nuxt 有很关键的一点就是 约定优于配置 。 page 目录下的所有 *.vue 文件会自动生成路由配置。

在项目初始化之后,在 pages 下面默认有一个 index.vue 文件,所以当我们使用 npm run dev 启动项目,并且使用 http://localhost:3000/ 访问的时候能够正常访问路由。

为了证实上面这一点,在 pages 下面创建一个信息 about.vue 文件,并且 http://localhost:3000/about 去访问刚刚写的页面。我们可以按照正常的 Vue 页面去开发就好了。

page目录

├─page
│ ├─index.vue
└───└─about.vue

about.vue

<template>
 <div>
  <h2>This About</h2>
 </div>
</template>

创建完成之后使用 http://localhost:3000/about 访问该页面,页面能够正常的渲染出来了。就会看到 This About 显示在页面中。

做到这一步之后就应该实现路由之间的跳转了。 Vue 开发过程中,都是使用 router-link 标签完成路由之间的跳转,在 Nuxt 也同样可以使用 router-link ,但是 Nuxt 仍然推荐使用 nuxt-link , nuxt-link 与 router-link 的功能是等效的。

可能会有一些疑问,既然是等效的,为什么要使用 nuxt-link 呢?官方文档中是这样说的:将来我们会为 nuxt-link 组件增加更多的功能特性,例如资源预加载,用于提升 nuxt.js 应用的响应速度。显然嘛,官方不会无缘无故的就做出这么一个东西来,肯定实在其中做了很多的优化工作的。

稍微的改动一下刚才的 about.vue 在里面添加两个标签,一个使用 nuxt-link ,一个使用 router-link 看下能否正常完成跳转。

about.vue - 更改后

<template>
 <div>
  <h2>This About</h2>
  <nuxt-link to="/">首页</nuxt-link>
  <router-link to="/">首页</router-link>
 </div>
</template>

既然从路由开始那么就不得不说到子路由,全局路由守卫这些都些在路由中经常用到的应该怎么处理?该怎么解决这些问题。

前面既然说到了 Nuxt 会把 pages 文件夹下面的所有 *.vue 文件编译成路由,那么子路由需要使用文件夹嵌套才行。

接下来就尝试一下。首先要更改一下 pgeas 目录结构。

page目录

├─page
│ ├─about
│ │ ├─detail.vue
│ │ └─index.vue
└───└─index.vue

注意上面的 about 目录,是 index.vue 而并非 about.vue ,这里的 index.vue 指的是 about 路由下的首页,也就是最开始放在与 index.vue 同级的那个 about.vue 是一样的效果。

about/index.vue

<template>
 <div>
  <h2>This About</h2>
  <nuxt-link to="/">首页</nuxt-link>
  <router-link to="/">首页</router-link>
 </div>
</template>
about/detail.vue
<template>
 <div>
 <h2>This Detail</h2>
 </div>
</template>

现在如果我们想要访问刚才的那两个路由地址分别就是 http://localhost:3000/about 和 http://localhost:3000/about/detail 就能看到刚才编写的 page 页面了。

如果想要看路由生成到底是什么样子的?可以在根目录下有一个 .nuxt 文件夹,在里面可以看到一个 router.js ,这个文件夹下面就是 Nuex 生成好的路由信息。

打开文件后翻到最后会有一段这样的代码,是不是很眼熟?这是不就是在编写 Vue 项目的时候配置的哪些路由文件么?

router.js

export function createRouter() {
 return new Router({
 mode: 'history',
 base: decodeURI('/'),
 linkActiveClass: 'nuxt-link-active',
 linkExactActiveClass: 'nuxt-link-exact-active',
 scrollBehavior,
 routes: [{
  path: "/about",
  component: _9ceb4424,
  name: "about"
 }, {
  path: "/about/detail",
  component: _18146f65,
  name: "about-detail"
 }, {
  path: "/",
  component: _d3bf5a4e,
  name: "index"
 }],
 fallback: false
 })
}

有了这个文件的话我们就可以清楚的知道,路由的结构了。不仅仅这样,还可以使用 name 去实现路由的跳转了。

需要注意的是,如果你的路由是有文件夹嵌套的话, Nuxt 是用使用 - 来拼接路由的 name 名称的(如: about-detail ),但是文件夹内部的 index.vue 会直接已文件夹的名字作为 name 。一旦知道了路由的 name ,这样我们就可以使用命令的方式跳转路由了。

再次更改一下 about/index.vue 。

about/index.vue

<template>
 <div>
  <h2>This About</h2>
  <nuxt-link :to="{name:'about-detail'}">详情</nuxt-link>
  <router-link :to="{name:'index'}">首页</router-link>
  <button @click="onClick">跳转到详情</button>
 </div>
</template>
<script>
export default {
 methods: {
 onClick() {
  this.$router.push({name:"about-detail"})
 }
 }
}
</script>

使用路由访问 http://localhost:3000/about 地址,分别点击详情、首页与 button ,都是能够正常跳转的,与之前的 Vue 开发是完全没有任何区别的。在 vue-router 中有一个很重要的一个点就是 动态路由 的概念,如果想要实现动态路由应该怎么处理呢?

如果想要在 Nuxt 中使用动态路由的话,需要在对应的路由下面添加一个 _参数名.vue 的文件,在 about 文件下面添加一个 _id.vue

page目录

├─page
│ ├─about
│ │ ├─detail.vue
│ │ ├─_id.vue
│ │ └─index.vue
└───└─index.vue

新建完成之后在去 router.js 中看一下更改后的路由结构

export function createRouter() {
 return new Router({
 mode: 'history',
 base: decodeURI('/'),
 linkActiveClass: 'nuxt-link-active',
 linkExactActiveClass: 'nuxt-link-exact-active',
 scrollBehavior,
 routes: [{
  path: "/about",
  component: _9ceb4424,
  name: "about"
 }, {
  path: "/about/detail",
  component: _18146f65,
  name: "about-detail"
 }, {
  path: "/about/:id",
  component: _6b59f854,
  name: "about-id"
 }, {
  path: "/",
  component: _d3bf5a4e,
  name: "index"
 }],
 fallback: false
 })
}

可以明显的看到在 /about/:id 这个路由,明显的变化不止这些变动的还有 name: "about-id" 不再是之前的 name:about 了。如果想要使用这个 id 的话必须在 _id.vue 中才能获取到。

**_id.vue**

<template>
 <div>
 {{$route.params.name}}
 {{$route.params.id}}
 </div>
</template>

在 _id.vue 中编写以上代码并使用 http://localhost:3000/about/ABC ,可以看到在页面中已经展示了当前的 id 值。

在实际开发过程当中可能 params 可能会有多个参数,又应该怎么处理呢?

调整目录结构

// id为可选参数
├─page
│ ├─about
│ │ ├─_name
| | | └─_id
| | |  └─index.vue
│ │ └─index.vue
└───└─index.vue

**about - _name - _id.vue**

<template>
 <div>
 {{$route.params.name}}
 {{$route.params.id}}
 </div>
</template>

弄完之后看下 router.js 的变化

export function createRouter() {
 return new Router({
 mode: 'history',
 base: decodeURI('/'),
 linkActiveClass: 'nuxt-link-active',
 linkExactActiveClass: 'nuxt-link-exact-active',
 scrollBehavior,
 routes: [{
  path: "/about",
  component: _9ceb4424,
  name: "about"
 }, {
  path: "/about/detail",
  component: _18146f65,
  name: "about-detail"
 }, {
  path: "/about/:name",
  component: _2ec9f53c,
  name: "about-name"
 }, {
  path: "/about/:name/:id",
  component: _318c16a4,
  name: "about-name-id"
 }, {
  path: "/",
  component: _d3bf5a4e,
  name: "index"
 }],
 fallback: false
 })
}

这里展示的是第二种情况, id 为必选参数的情况,路由被编译的结果。

虽然路由已经添加了参数,但是 id 属性不是必填属性,这样的话不能满足项目需求又要如何处理呢?很简单的,在 _id.vue 文件同目录下添加一个 index.vue 文件就可以了。

// id为必选参数
├─page
│ ├─about
│ │ ├─_name
| | | ├─_id.vue
| | | └─index.vue
│ │ └─index.vue
└───└─index.vue

需要注意的是,一定要在 _id.vue 文件中使用传入的参数,直接获取在 index.vue 中是拿不到任何信息的。但是如果访问 http://localhost:3000/about/ABC 这样的路由的话,实在 index.vue 中是可以获取到 name 参数的。

在刚才的 router.js 文件中生成的所有的路由都是平级的,如何实现路由的嵌套,如果想要实现嵌套路由的话,必须有和当前路由同名的文件夹存在,才能完成路由的嵌套。

page目录

├─page
│ ├─about
| | ├─_id.vue
| | └─detaile.vue
│ ├─about.vue
└───└─index.vue

router.js

export function createRouter() {
 return new Router({
 mode: 'history',
 base: decodeURI('/'),
 linkActiveClass: 'nuxt-link-active',
 linkExactActiveClass: 'nuxt-link-exact-active',
 scrollBehavior,
 routes: [{
  path: "/about",
  component: _76687814,
  children: [{
  path: "",
  component: _9ceb4424,
  name: "about"
  }, {
  path: ":id",
  component: _6b59f854,
  name: "about-id"
  }]
 }, {
  path: "/",
  component: _d3bf5a4e,
  name: "index"
 }],
 fallback: false
 })
}

更改完目录结构,那我们嵌套的路由应该如何展示?在 vue.js 中开发的时候使用 router-view 这个标签完成的。为了性能的优化 Nuxt 也提供了一个对应的标签 nuxt-child 。

如果想实现嵌套路由传参需要稍微的改动一下目录结构,按照上面的方法实现就好了,下面是一个路由结构的例子。

page目录

├─page
│ ├─about
│ │ ├─detail
| | | ├─_id.vue
| | | └─index.vue
│ │ └─index.vue
└───└─index.vue

router.js

export function createRouter() {
 return new Router({
 mode: 'history',
 base: decodeURI('/'),
 linkActiveClass: 'nuxt-link-active',
 linkExactActiveClass: 'nuxt-link-exact-active',
 scrollBehavior,
 routes: [{
  path: "/about",
  component: _76687814,
  name: "about",
  children: [{
  path: "detail",
  component: _0a09b97d,
  name: "about-detail"
  }, {
  path: "detail/:id?",
  component: _fa7c11b6,
  name: "about-detail-id"
  }]
 }, {
  path: "/",
  component: _d3bf5a4e,
  name: "index"
 }],
 fallback: false
 })
}

在 _id.vue 中则可以使用id这个参数了。访问路由 http://localhost:3000/about/detail/123 ,依然可以拿到传入的 id 为 123 的这个参数。

说了这么多了,还有很多问题没得说完,关于路由的全局守卫又应该如何去使用?在 Nuxt 根目录下有个 plugins 文件夹。首先要做的是在里面创建一个名为 router.js 文件。

plugins-router.js

export default ({app}) => {
 app.router.beforeEach((to,form,next) => {
 console.log(to)
 next();
 });
}

导出了一个函数,在这个函数中可以通过结构拿到 vue 的实例对象名叫 app 。需要注意的是,这个 beforeEach 函数的执行,有可能会在服务端也会有可能在客户端输出。客户端首次访问的页面会在服务端做输出,一旦渲染完成之后,则不会再在服务端输出,则会一直在客户端进行输出了。

说到这里做个小插曲,那么又该怎么区分当前是在客户端环境还是服务端环境呢?可以使用 process.server 获取到当前的运行环境,其得到的是 Boolean 值, true 服务端, fasle 客户端。

做了这些之后去访问路由,仿佛没有任何输出,无论实在客户端还是在服务端,都没有任何打印输出,中间缺少了步骤,需要在根目录下找到 nuxt.config.js 对插件进行配置。

nuxt.config.js

const pkg = require('./package')
module.exports = {
 plugins: [
 '@/plugins/element-ui',
 '@/plugins/router'
 ]
}

由于更改了 Nuxt 配置需要重启一下服务,才能正常执行刚刚写入的插件。然后访问刚刚写入的路由,会看在服务端初次渲染的时候,会输出我们想要的那些东西,进行路由跳转的话,会在客户端输出,这也就证明了 Nuxt 只会做首屏的服务器渲染。

路由说了这么接下来需要说一下 Nuxt 是如何为指定的路由配置数据做渲染的。其实 Nuxt 在做渲染的时候包裹了很多层。首先有一个 Document 作为其模板,然后再去寻找其布局的页面,找到对应的页面之后,再根据引用去找到相关的组件进行渲染,数据请求与数据挂载,一系列完成之后,把剩余的路由信息返还给客户端,渲染完成,这个就是 Nuxt 简单的渲染流程。

在上面提到了一个 布局页面 ,这个东西应该去哪里找?又应该怎么做呢?它对于项目而言对于开发又有什么好处?在 Nuxt 根目录下有一个 layouts 文件夹,下面有一个 default.vue 这个文件就是上面提到的渲染页面,也就同等于 vue 开发中的 App.vue ,在这里可以做很多事情。例如添加一个全局的导航。

在 layouts 文件夹添加一个 about.vue 文件写入如下内容,接下来需要在 pages 下面的 about.vue 中通知,对应 pages 使用哪个布局页面,不写则使用默认,然后访问 http://localhost:3000/about 相关的页面,只要是和 about 相关的页面,都会展示这个内容。

layouts - about.vue

<template>
 <div>
 <h2>Aaron 个人博客主页</h2>
 <nuxt></nuxt>
 </div>
</template>

pages - about.vue

<template>
 <div>
  <h2>About</h2>
  <nuxt-child></nuxt-child>
 </div>
</template>
<script>
export default {
 layout:"about"
}
</script>

访问一下所有与 about 页面有关的页面,都会看到 Aaron个人博客主页 这个字样,若访问根路由则无法看到的。

如果做过 mvc 开发的话,如果页面发生错误会跳转到一个错误页面的。 Nuxt 也是有默认的错误页面的,但是全是英文而且样式也不太好看,不能自定义样式。如何自定义错误页面呢?

在 layouts 文件夹中新建一个 error.vue 文件。

layouts - error.vue

<template>
 <div>
  <h1>这里是错误页面</h1>
  <h2 v-if="error.statusCode == 404">404 - 页面不存在</h2>
  <h2 v-else>500 - 服务器错误</h2>
  <ul>
    <li><nuxt-link to="/">HOME</nuxt-link></li>
  </ul>
 </div>
</template>

<script>
export default {
 props:["error"]
}
</script>

在 error.vue 中可以通过 props 拿到一个 error 对象,获取到 error 错误信息之后能做任何想要做的事情。需要注意的一点是,自定意的错误页面,只能在客户端访问失效的时候才会响应到该页面,若在服务端的话,是无法直接渲染这个页面的。

更改页面配置 Nuxt 中有些全局的配置,配置信息在 nuxt.config.js 更改其全局配置, pages 文件夹中的 *.vue 文件也是可以配置的,页面私有的配置会覆盖掉全局的配置。

举例:

export default {
 layout:"about",
 head: {
  title:"About"
 }
}

在这些全局配置中最重要的一个就是 asyncData 这个属性。 asyncData 到底是用来做什么的呢?这个数据可以在设置组件的数据之前能一步获取或者处理数据。也就是说在组件渲染之前先获取到数据,然后等待挂载渲染。

举个例子:

<template>
 <div>
   <h2>姓名:{{userInfo.name}}</h2>
   <h2>年龄:{{userInfo.age}}</h2>
   <nuxt-child></nuxt-child>
 </div>
</template>
<script>
let getUserInfo = () => {
 return new Promise(resolve => {
  setTimeout(() => {
   let data = {"name":"Aaron","age":18};
   resolve(data);
  })
 })
}
export default {
 layout:"about",
 head: {
  title:"About"
 },
 async asyncData(){
  const userInfo = await getUserInfo();
  return {userInfo}
 }
}
</script>

一定要 return 出去获取到的对象,这样就可以在组件中使用,这里返回的数据会和组件中的 data 合并。这个函数不光在服务端会执行,在客户端同样也会执行。

注意事项:

1.asyncData 方法会在组件(限于页面组件)每次加载之前被调用
2.asyncData 可以在服务端或路由更新之前被调用
3.第一个参数被设定为当前页面的上下文对象
4.Nuxt会将 asyncData 返回的数据融合到组件的data方法返回的数据一并返回给组件使用
5.对于 asyncData 方式实在组件初始化前被调用的,所以在方法内饰没办法通过this来引用组件的实例对象

刚刚提到了一点就是上下问对象,在上线文对象中可以获取到很多东西,如路由参数,错误信息等等等,这里就不作太多赘述了,有了这些可以做一些页面的重定向或者其他工作,比如参数校验,安全验证等工作。

路由扯了一大堆,接下来说一下如何在 Nuxt 中融入 axios 的使用。

安装 axios

npm install @nuxtjs/axios --save-dev

安装完成后更改配置信息:

nuxt.config.js

module.exports = {
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
  ],
  axios: {
    proxy:true // 代理
  },
  proxy: {
    "/api/":"http://localhost:3001/"  // key(路由前缀):value(代理地址)
  }
}

主要说名一下 proxy 这里, /api/ 在请求的时候遇到此前缀则会只指向的代理地址去请求数据。

既然说到了 axios ,就不得不提到的一个东西就是拦截器,很是有用在项目开发过程中必不可少的。

举个例子:

module.expores{
  plugins: [
    '@/pluginx/axios'
  ]
}
plugins/axios.js
export default ({$axios,request}) => {
  $axios. onRequest((config) => {
    config.headers.token = "Aaron"
  })
}

总结

说了这么多也许会有些纰漏,或者遗漏的知识点,若有什么错误的地方可以留言,尽快做出改正。谢谢大家花费这么长时间阅读这篇文章。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
YUI 读码日记之 YAHOO.lang.is*
Mar 22 Javascript
top.location.href 没有权限 解决方法
Aug 05 Javascript
JavaScript学习笔记(十)
Jan 17 Javascript
js 多浏览器分别判断代码
Apr 01 Javascript
JSON中key动态设置及JSON.parse和JSON.stringify()的区别
Dec 29 Javascript
使用vue.js实现联动效果的示例代码
Jan 10 Javascript
mac中利用NVM管理不同node版本的方法详解
Nov 08 Javascript
用原生 JS 实现 innerHTML 功能实例详解
Apr 03 Javascript
jQuery HTML获取内容和属性操作实例分析
May 20 jQuery
vue中后端做Excel导出功能返回数据流前端的处理操作
Sep 08 Javascript
WebWorker 封装 JavaScript 沙箱详情
Nov 02 Javascript
react 路由Link配置详解
Nov 11 Javascript
vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果
Apr 08 #Javascript
面试题:react和vue的区别分析
Apr 08 #Javascript
vue router 组件的高级应用实例代码
Apr 08 #Javascript
JavaScript中的一些实用小技巧总结
Apr 07 #Javascript
详解vue 不同环境配置不同的打包命令
Apr 07 #Javascript
JavaScript数组去重的几种方法
Apr 07 #Javascript
vue表单验证你真的会了吗?vue表单验证(form)validate
Apr 07 #Javascript
You might like
《魔兽争霸3:重制版》翻车了?你想要的我们都没有
2019/11/07 魔兽争霸
php中使用exec,system等函数调用系统命令的方法(不建议使用,可导致安全问题)
2012/09/07 PHP
使用Curl进行抓取远程内容时url中文编码问题示例探讨
2013/10/29 PHP
ThinkPHP3.2.2的插件控制器功能
2015/03/05 PHP
redirect_uri参数错误的解决方法(必看)
2017/02/16 PHP
Laravel5.7 Eloquent ORM快速入门详解
2019/04/12 PHP
php使用pthreads v3多线程实现抓取新浪新闻信息操作示例
2020/02/21 PHP
Ext JS 4实现带week(星期)的日期选择控件(实战二)
2013/08/21 Javascript
javascript按位非运算符的使用方法
2013/11/14 Javascript
JS动态修改iframe高度和宽度的方法
2015/04/01 Javascript
bootstrap 模态框(modal)实现水平垂直居中显示
2017/01/23 Javascript
基于 D3.js 绘制动态进度条的实例详解
2018/02/26 Javascript
Angular5中提取公共组件之radio list的实例代码
2018/07/10 Javascript
vue移动端的左右滑动事件详解
2020/06/17 Javascript
浅谈vue使用axios的回调函数中this不指向vue实例,为undefined
2020/09/21 Javascript
vue点击Dashboard不同内容 跳转到同一表格的实例
2020/11/13 Javascript
用webAPI实现图片放大镜效果
2020/11/23 Javascript
详解Vue3 Teleport 的实践及原理
2020/12/02 Vue.js
python使用百度翻译进行中翻英示例
2014/04/14 Python
简单介绍Python中的filter和lambda函数的使用
2015/04/07 Python
python smtplib模块发送SSL/TLS安全邮件实例
2015/04/08 Python
nginx黑名单和django限速,最简单的防恶意请求方法分享
2019/08/09 Python
Python处理mysql特殊字符的问题
2020/03/02 Python
解决django FileFIELD的编码问题
2020/03/30 Python
python 制作本地应用搜索工具
2021/02/27 Python
Omio美国:全欧洲低价大巴、火车和航班搜索和比价
2017/11/08 全球购物
公司中秋节活动方案
2014/02/12 职场文书
2014学校庆三八妇女节活动总结
2014/03/01 职场文书
电子商务专业应届生求职信
2014/05/28 职场文书
感恩教师节演讲稿
2014/09/03 职场文书
法人授权委托书范本
2014/09/17 职场文书
民政局个人整改措施
2014/09/24 职场文书
2014年租房协议书范本
2014/10/30 职场文书
自我检讨书怎么写
2015/05/07 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
python3操作redis实现List列表实例
2021/08/04 Python