前言

本篇主要针对nuxtjs中的一些重要概念整理和代码实现!

在学习vue服务端渲染之前,先搞清楚几个概念:

  • 什么是客户端渲染(CSR)
  • 什么是服务端渲染(SSR)
  • CSR和SSR有什么异同

客户端渲染(CSR):当用户在浏览器中输入网址,打开网页,此时的页面只有样式和一些html代码构成的空壳页面,并没有数据。这就需要我们通过执行js代码,请求相关数据,请求到数据之后,通过模板(vue),将这些数据渲染到页面,最终呈现给用户完整的页面。

服务端渲染(SSR):当用户在浏览器中输入网址,打开网页,此时的后端会根据请求的网址,拿到相关页面并将数据填入到页面,完成页面的渲染,最后将完整的页面返回给客户端(浏览器),呈现到用户面前。

利弊:客户端渲染,就是现在非常流行的前后分离的开发模式,极大程度的减轻后端压力,但是对SEO不友好;服务端渲染对SEO友好,但是对后端以及服务器的性能要求较高。

正文:

先用node实现简易的服务端渲染:

const Vue = require('vue');
const server = require('express')(); server.get('/',(req,res)=>{ //创建 Vue 实例
const app = new Vue({
template:`<div>hello vue SSR!</div>`
}) // 创建 renderer 没下载的需要手动下载 npm i vue-server-renderer --save改包用于vue的服务端渲染
const renderer = require('vue-server-renderer').createRenderer(); renderer.renderToString(app).then(html=>{
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
${html}
</body>
</html>
`)
}).catch(err=>{
console.log(err)
})
}) server.listen(8001)

检查源代码:

下面使用nuxjs来构建项目!

首先,使用nuxt提供的脚手架( create-nuxt-app)构建项目,根据提示一步一步操作。

主要注意俩点:1.Choose custom server framework,选择服务端渲染的框架,我们这里选择的是express

        2.Choose rendering mode 这个选项可以选择SSR和Single Page APP,因为我们需要构建服务端渲染多页面项目,所以这里选择SSR

下面就是项目创建完成后的目录结构:

目录结构:

  • assets::资源目录,用于组织未编译的静态资源(css文件、图标等)
  • components: 组件目录,用于组织应用的vue组件,这些组件不会像页面组件(pages中的组件)那样有 asyncData 方法的特性
  • layouts:布局目录,用于组织应用的布局组件,若无额外配置,该目录不能被重命名
  • middleware:中间件目录,用于存放应用的中间件
  • pages: 页面目录,用于组织应用的路由及视图,Nuxt.js 框架读取该目录下所有的 .vue文件并自动生成对应的路由配置,若无额外配置,该目录不能被重命名
  • plugins:插件目录,用于组织那些需要在 根vue.js应用实例化之前需要运行的JavaScript插件
  • server:服务器目录,用于服务端渲染
  • static:静态文件目录,用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下(/static/robots.txt 映射至 /robots.txt),若无额外配置,该目录不能被重命名
  • store:store目录,用于组织应用的 Vuex状态树 文件,若无额外配置,该目录不能被重命名
  • nuxt.config.js:用于组织 Nuxt.js 应用的个性化配置,以便覆盖默认配置,若无额外配置,该目录不能被重命名
  • package.json:用于描述应用的依赖关系和对外暴露的脚本接口,该文件不能被重命名

项目跑起来:

检查源码,页面的所有数据都能够看到。

接下来正式学习nuxt,主要分为3部分:

  • 生命周期
  • 路由
  • vuex状态树

一、生命周期(钩子函数)

  • nuxtServerInit => 服务器初始化
  • middleware => 中间件
  • validate() => 参数校验
  • asyncData()和fetch() => 异步数据处理
  • render => 客户端渲染
  • beforeCreate和created => 这俩个钩子服务端和客户端都存在,可以拿到服务端的上下文以及组件本身

下面具体使用这些生命周期:

1.nuxtServerInit 

在store目录中新建index.js:

export const actions = {
nuxtServerInit(store, context){
console.log('我是nuxtServerInit生命周期!', store, context)
}
}

项目跑起来后,在终端会打印相关信息:

2.middleware 

middleware中间件可以在配置文件中、布局页面、页面组件中使用。

在nuxt.config.js配置文件中,将middleware写进router配置中,可以进行全局的导航守卫:

module.exports = {
mode: 'universal',
router: {
middleware: 'isLogin'
},
...

接下来在middleware目录中新建isLogin.js文件:

export default (context) => {
console.log('我是全局守卫')
}

打印:

在布局页面中使用中间件,在layout目录下的default.vue中:

<script>
export default {
// middleware: 'isLogin',
middleware(){
console.log('我是布局页面的中间件!')
},
}
</script>

在页面组件内中间件可以直接写外部之前定义的中间件,也可以用中间件钩子!

同样,在页面组件中,pages目录下的index.vue中:

<script>
export default {
// middleware: 'isLogin',
middleware(){
console.log('我是页面组件的中间件!')
},
}
</script>

通过打印的顺序可以看出,中间件的执行顺序是:nuxt.config.js文件 => 布局文件 => 页面文件,所有中间件函数的参数中,都能拿到服务端的上下文信息context。

3.validate()

validate()需要定义在页面组件内,即pages目录下的页面,可以用来进行参数校验,拦截是否可以进入该页面。具体实现,在pages/index.vue中:

//js 代码
export default {
// middleware: 'isLogin',
middleware(){
console.log('我是页面组件的中间件!')
},
validate(context){
const {params, query} = context;
//对传过来的params和query进行校验
//...
console.log('我是页面validate参数校验')
return true; // 校验通过,校验失败返回false,进入到404页面
},
components: {
Logo
}
}

 4.asyncData()和fetch()

这俩个钩子也是在页面组件中使用,pages/index.vue中:

  asyncData(context){
// 异步读取数据,处理相关业务逻辑
console.log('我是asyncData')
//最后返回数据
return {
a: 1
};
}, fetch(context){
// 异步读取数据,处理相关业务逻辑,将数据提交给vuex
console.log('我是fetch')
},

5.beforeCreate()和created()

在pages/index.vue中:

...
fetch(context){
// 异步读取数据,处理相关业务逻辑,将数据提交给vuex
console.log('我是fetch')
},
beforeCreate() {
console.log('我是beforeCreate')
},
created() {
console.log('我是create')
},
...

客户端:

服务端:

二、路由(约定式、自定义)

1.约定式路由:依据pages目录结构自动生成 vue-router模块的路由配置。

1>基础路由:

在layouts目录中的default.vue:

<template>
<div>
<!-- 声明式跳转:nuxt-link跟vue中的router-link一样,负责路由跳转 -->
<nuxt-link to="/">首页</nuxt-link>
<nuxt-link to="/tabA">tabA</nuxt-link>
<nuxt-link to="/tabB">tabB</nuxt-link> <!-- 展示区,跟vue中的router-view一样 -->
<nuxt/>
</div>
</template>

效果:

nuxt自动生成的路由为:

router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'tabA',
path: '/tabA',
component: 'pages/tabA.vue'
},
{
name: 'tabB',
path: '/tabB',
component: 'pages/tabB.vue'
}
]
}

2>动态路由

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

pages/tabA.vue:

<template>
<div>
<h3>
tabA
</h3>
<!-- 俩种路由传参方式 -->
<nuxt-link to="/tabA/1?a=tabA1">tabA动态路由1</nuxt-link>
<!-- name: 'tabA-id',这里-id必须是tabA目录下的_id.vue的名称,且将 _ 改为 - -->
<nuxt-link :to="{name: 'tabA-id', params:{id: 2}, query:{a: 'tabA2'}}">tabA动态路由2</nuxt-link> <!-- 子路由的展示区 -->
<nuxt/>
</div>
</template>

pages/tabA/_id.vue:

<template>
<div>
<h3>我是tabA的动态路由页</h3>
</div>
</template>

效果:

这里会发现一个问题,路由为/tabA的时候,也打开了动态路由页,这是因为当路由为tabA时,会发现它下面还有子路由,由于没有指定默认的页面,因此会找到一个子路由进行渲染。为了解决这个问题,可以在tabA目录下新建一个index.vue。

tabA/index.vue:

<template>
<h3>我是动态子路由默认页</h3>
</template>

此时nuxt自动生成的路由信息为:

router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'tabA-id',
path: '/tabA/:id',
component: 'pages/tabA/_id.vue'
},
...
]
}

3>嵌套路由

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

在tabA目录下新建tabC目录和tabC.vue

tabA/tabC.vue:

<template>
<div>
<!-- 子路由展示区 -->
<nuxt></nuxt>
</div>
</template>

tabA/tabC/index.vue:

<template>
<div>
<h3>我是tabC</h3>
</div>
</template>

从上图可以看到俩种颜色的区域,是俩级展示区,通常我们一个页面只需要一个展示区,因此需要改一下项目的目录结构:

在pages目录下的页面为一级展示区,其中有tabA、tabB,接下来将tabA目录下的页面也设置成一级展示区。将tabA.vue内容拷贝至tabA目录下的index.vue中,并删除tabA.vue。

2、扩展路由

nuxt中支持声明式路由,同样也支持自定义路由,根据路由渲染指定的视图,需要在nuxt.config.js中配置:

router: {
extendRoutes(routes, resolve){
routes.push({
name: 'tabD',
path: '/tabD',
component: resolve(__dirname, 'pages/extendTabD.vue')
})
}
},

在pages中新建extendTabD.vue:

pages/extendTabD.vue

<template>
<div>
<h3>我是扩展路由tabD</h3>
</div>
</template>

在layouts/default.vue中增加链接: <nuxt-link to="/tabD">tabD</nuxt-link>

3、路由过渡动画

1>全局过渡动画:

transition.css:

/*  路由统一动效 */

/* 动画形式 */
.page-enter-active, .page-leave-active{
transition: opacity .5s;
} /* 入 退 */
.page-enter, .page-leave-active{
opacity: 0;
}

nuxt.config.js:

/*
** Global CSS
*/
css: [
'assets/css/transition.css'
],

2>局部过渡动画

extendTabD.vue:

<template>
<div>
<h3>我是扩展路由tabD</h3>
</div>
</template> <script>
export default {
name: "extendTabD",
transition: 'tabd'
}
</script> <style scoped>
.tabd-enter-active, .tabd-leave-active{
transition: .5s ease all;
}
.tabd-enter, .tabd-leave-active{
margin-left: -1000px;
}
</style>

 4、路由守卫

  • 前置
  • 后置
  • 组件内部

1>前置:

全局守卫:

  • 在nuxt.config.js的router内引入middleware
  • 在layouts文件中定义middleware
  • 在插件中定义前置全局守卫,beforeEach()

组件独享守卫:

  • 在页面中引入middleware或middleware()
  • 跟vue-router一样的组件内部钩子函数,比如:beforeRouteEnter

2>后置:

在插件中定义后置全局守卫,afterEach()

具体实现:

全局守卫:

a.借助middleware实现前置的路由守卫,在生命周期的middleware中已经基本实现。

middleware/isLogin.js:

export default (context) => {
console.log('我是全局守卫')
const {redirect} = context
redirect('/tabD') // 利用redirect()页面重定向
}

b.借助插件实现前置、后置全局守卫:

plugins/router.js

export default ({app, redirect})=>{
//app == vue实例
//全局前置守卫,进入的页面不是tabA的页面,都会重定向到tabA
app.router.beforeEach((to, from, next)=>{
if(to.name == 'tabA'){
next()
}else{
redirect({name: 'tabA'})
}
});
//全局后置守卫
app.router.afterEach((to, from)=>{
console.log('我是后置守卫')
})
}

nuxt.config.js中配置plugins:

plugins: [
'~/plugins/router.js'
],

组件内守卫:

extendTabD.vue:

<template>
<div>
<h3>我是扩展路由tabD</h3>
</div>
</template> <script>
export default {
name: "extendTabD",
transition: 'tabd',
beforeRouteEnter(to, from, next){
alert('haha');
}
}
</script>

三、vuex状态树

nuxt中集成了vuex,因此我们在项目中不需要下载引入,而是按照约定使用:

nuxt会尝试找到 src 目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:

  1. 引入vuex模块
  2. 将vuex模块加到vendors构建配置中
  3. 设置vue根实例的store配置项

nuxt中的vuex使用方式跟vue中大同小异,除了不需要手动引入之外,在模块化中,store目录下的每个子模块(除了根模块index.js),都会被转换成为状态树指定命名的子模块,其它使用方式跟vue项目中的vuex基本相同。

store/index.js:

const state = ()=>({

})
const mutations = { } const actions = { }
export default {
state,
mutations,
actions
}

store/user.js:

const state = () => ({
isLogin: false
}) const mutations = {
change_Login(state, isLogin) {
state.isLogin = isLogin
}
} const actions = {
changeLogin({commit},isLogin) {
commit('change_Login', isLogin)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}

pages/tabB.vue:

<template>
<div>
<h3>tabB</h3>
<h4>登录状态:{{isLogin ? '已登录' : '未登录'}}</h4>
</div>
</template> <script>
import { mapState } from 'vuex';
export default {
name: "TabB",
computed:{
...mapState({
isLogin: state => state.user.isLogin
})
},
created() {
setTimeout(()=>{
this.$store.dispatch('user/changeLogin', true)
}, 1000)
}
}
</script>

效果:一秒后,登录状态从“未登录”变为“已登录”。

以上就是我对nuxt学习过程中一些重要概念的理解和实践,有什么不足欢迎评论指正!

脚踏实地行,海阔天空飞

vue服务端渲染之nuxtjs的更多相关文章

  1. [vue] vue服务端渲染nuxt.js

    初始化 使用脚手架工具 create-nuxt-app 快速创建 npx create-nuxt-app <项目名> npx create-nuxt-app 执行一些选择 在集成的服务器端 ...

  2. Egg + Vue 服务端渲染工程化实现

    在实现 egg + vue 服务端渲染工程化实现之前,我们先来看看前面两篇关于Webpack构建和Egg的文章: 在 Webpack工程化解决方案easywebpack 文章中我们提到了基于 Vue ...

  3. vue服务端渲染axios预取数据

    首先是要参考vue服务端渲染教程:https://ssr.vuejs.org/zh/data.html. 本文主要代码均参考教程得来.基本原理如下,拷贝的原文教程. 为了解决这个问题,获取的数据需要位 ...

  4. vue服务端渲染简单入门实例

    想到要学习vue-ssr的同学,自不必多说,一定是熟悉了vue,并且多多少少做过几个项目.然后学习vue服务端渲染无非解决首屏渲染的白屏问题以及SEO友好. 话不多说,笔者也是研究多日才搞明白这个服务 ...

  5. vue服务端渲染提取css

    vue服务端渲染,提取css单独打包的好处就不说了,在这里主要说的是抽取css的方法 要从 *.vue 文件中提取 CSS,可以使用 vue-loader 的 extractCSS 选项(需要 vue ...

  6. 解析Nuxt.js Vue服务端渲染摸索

    本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. Nuxt.js 十分简单易用.一个简单 ...

  7. Nuxt.js vue服务端渲染

    一.为什么要用Nuxt.js 原因其实不用多说,就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题. 二.Nuxt.js和纯Vue项目的简单对比 1. build后目标产物不同 vue ...

  8. 实例PK(Vue服务端渲染 VS Vue浏览器端渲染)

    Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...

  9. Vue服务端渲染和Vue浏览器端渲染的性能对比

    Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...

随机推荐

  1. POJ 1681 高斯消元 枚举自由变元

    题目和poj1222差不多,但是解法有一定区别,1222只要求出任意一解,而本题需要求出最少翻转次数.所以需要枚举自由变元,变元数量为n,则枚举的次数为1<<n次 #include < ...

  2. CentOS7-磁盘扩容(LVM-非空目录拓展卷空间大小)

    查看存储情况 $ df -kh 查看磁盘情况 $ fdisk -l 创建分区(注:可操作存储上限2TB) $ fdisk /dev/sdb 根据提示,依次输入"n","p ...

  3. Docker:Docker的Run命令使用时报错

    命令报错:WARNING: Your kernel does not support swap limit capabilities, memory limited without swap. 这是因 ...

  4. HTTP:HTTP请求头和响应头详解

    HTTP请求报文 HTTP 请求报文由3部分组成(请求行+请求头+请求体) HTTP请求报文分析  1. 是请求方法,GET和POST是最常见的HTTP方法,初次以外还包括 DELETE.HEAD.O ...

  5. bugkuCTF

    这题说实话我一脸懵逼,计网还没学的我,瑟瑟发抖,赶紧去百度. 思路分析: 涉及到域名解析,也就是dns服务,看了看writeup,都是修改host文件,百度了下host文件的作用,才明白了 host文 ...

  6. linux学习之路第八天(linux文件权限详解)

    建议和我上一篇博客一起通读,效果更加 1.权限的基本介绍 通过一张图片解决疑惑(重点) rwx权限详解 rwx作用到文件 1)[r]代表可读(read) :可以读取,查看 2)[w]代表可写(writ ...

  7. postgresql行列转换

    --安装扩展 CREATE EXTENSION tablefunc --使用CROSSTAB函数 SELECT * FROM CROSSTAB('SELECT 主键, 需转换的列名, 需转换的列值 F ...

  8. 关键字abstract和static总结

    1.  abstract:意为抽象,在Java中可以修饰方法或者类 (1)修饰方法,这个方法是抽象方法,无方法体,这个类一定是抽象类,这个类的子类必须实现这个抽象方法: (2)修饰类,这个类一定是抽象 ...

  9. Linux | 搜索命令

    grep grep 命令用于在文本中执行关键词搜索,并显示匹配的结果,格式:grep[选项][文本] grep命令的参数及其作用 参数 作用 -b 将可执行文件当作文本文件对待 -c 公显示找到的行数 ...

  10. Docker 基础备忘录

    Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机).bare metal. ...