服务端预渲染之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 相关文章推荐
ArrayList类(增强版)
Apr 04 Javascript
三级下拉菜单的js实现代码
May 23 Javascript
jqPlot 图表中文API使用文档及源码和在线示例
Feb 07 Javascript
Javascript学习笔记之数组的构造函数
Nov 23 Javascript
原生JS和jQuery版实现文件上传功能
Apr 18 Javascript
浅谈jquery中使用canvas的问题
Oct 10 Javascript
JavaScript评论点赞功能的实现方法
Mar 13 Javascript
在 Angular 中实现搜索关键字高亮示例
Mar 21 Javascript
让Vue也可以使用Redux的方法
May 23 Javascript
js实现窗口全屏示例详解
Sep 17 Javascript
微信小程序分包加载代码实现方法详解
Sep 23 Javascript
js绘制一条直线并旋转45度
Aug 21 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
PHP字符串 ==比较运算符的副作用
2009/10/21 PHP
php中的注释、变量、数组、常量、函数应用介绍
2012/11/16 PHP
Codeigniter+PHPExcel实现导出数据到Excel文件
2014/06/12 PHP
PHP实现文件上传与下载实例与总结
2016/03/13 PHP
PHP实现QQ、微信和支付宝三合一收款码实例代码
2018/02/19 PHP
Javascript this关键字使用分析
2008/10/21 Javascript
一个JavaScript操作元素定位元素的实例
2014/10/29 Javascript
浅谈jQuery双事件多重加载的问题
2016/10/05 Javascript
教你一步步用jQyery实现轮播器
2016/12/18 Javascript
input获取焦点时底部菜单被顶上来问题的解决办法
2017/01/24 Javascript
JS前端开发判断是否是手机端并跳转操作(小结)
2017/02/05 Javascript
AngularJS路由Ui-router模块用法示例
2017/05/29 Javascript
解决VUEX刷新的时候出现数据消失
2017/07/03 Javascript
Angular4实现动态添加删除表单输入框功能
2017/08/11 Javascript
JS Testing Properties 判断属性是否在对象里的方法
2017/10/01 Javascript
解决vue中使用Axios调用接口时出现的ie数据处理问题
2018/08/13 Javascript
vue-rx的初步使用教程
2018/09/21 Javascript
Vue CLI项目 axios模块前后端交互的使用(类似ajax提交)
2019/09/01 Javascript
layui动态绑定事件的方法
2019/09/20 Javascript
NodeJS开发人员常见五个错误理解
2020/10/14 NodeJs
Python函数参数类型*、**的区别
2015/04/11 Python
深入浅析python中的多进程、多线程、协程
2016/06/22 Python
pyqt5 删除layout中的所有widget方法
2019/06/25 Python
Python自动生成代码 使用tkinter图形化操作并生成代码框架
2019/09/18 Python
利用python实现冒泡排序算法实例代码
2019/12/01 Python
如何基于python实现脚本加密
2019/12/28 Python
在python中logger setlevel没有生效的解决
2020/02/21 Python
Python抓包程序mitmproxy安装和使用过程图解
2020/03/02 Python
详解appium自动化测试工具(monitor、uiautomatorviewer)
2021/01/27 Python
Python xlwings插入Excel图片的实现方法
2021/02/26 Python
美国在线面料商店:Online Fabric Store
2018/07/26 全球购物
教育科学研究生自荐信
2013/10/09 职场文书
护士演讲稿优秀范文
2014/04/30 职场文书
新手上路标语
2014/06/20 职场文书
Nginx安装完成没有生成sbin目录的解决方法
2021/03/31 Servers
浅谈MySQL之浅入深出页原理
2021/06/23 MySQL