说接上文《二九║ Nuxt实战:异步实现数据双端渲染》,昨天我们经过项目二的首页数据处理,简单了解到了 nuxt 异步数据获取的做用,以及亲身体验了几个重要文件夹的意义,整篇文章也一直在往如何实现服务端渲染的方向讲解,由于我我的感受这个是一个重点,若是是只会如何使用的话,你们就能够蜻蜓点水的看看就好了,昨天呢,遗留了几个问题,我也想了想,尚未想好如何经过浅显的话来归纳,若是要是搬出来教科书似的讲解,感受又不是很清晰,我就在之后的领悟中补充吧,这里就先说下其中的三个问题:css
一、咱们经过 dev 编译,生成的 .nuxt 临时文件夹(我我的感受他就像咱们 .net core 中的 bin 文件夹),.nuxt 目录为 npm run dev或者是npm run build 后才生成,两个操做都执行了 build() 方法,用于存放 Nuxt.js 的核心库文件,若是你将一个老项目的 .nuxt 文件夹覆盖一个新项目的 .nuxt 文件夹,新项目正常运行,按照老的项目路由规则之类的均可以正常访问。例如,你能够在这个目录下找到 server.js
文件,描述了 Nuxt.js 进行服务端渲染的逻辑,流程是:调用 nuxtServerInit
方法,当请求打入时,最早调用的便是 nuxtServerInit
方法,能够经过这个方法预先将服务器的数据保存,如已登陆的用户信息等。另外,这个方法中也能够执行异步操做,并等待数据解析后返回。Middleware
层,通过第一步后,请求会进入 Middleware
层,在该层中有三步操做:读取 nuxt.config.js
中全局 middleware
字段的配置,并调用相应的中间件方法 匹配并加载与请求相对应的 layout
调用 layout
和 page
的中间件方法。调用 validate
方法,在这一步能够对请求参数进行校验,或是对第一步中服务器下发的数据进行校验,若是校验失败,将抛出 404 页面。html
调用 fetch
及 asyncData
方法,这两个方法都会在组件加载以前被调用,它们的职责各有不一样, asyncData
用来异步的进行组件数据的初始化工做,而 fetch
方法偏重于异步获取数据后修改 Vuex 中的状态。前端
二、每次修改文件,都会触发热 webpack 的[HMR] 热加载,由于 Nuxt.js集成了以下模块: Vue-Router, Vue-Meta 和 Vuex (仅在使用 Vuex 状态树配置项 时引入)。 这样的好处在于,不须要手工配置依赖,每次当咱们修改文件,webpack 就会自动保存,Nuxt.js 使用 Webpack 和 vue-loader 、 babel-loader 来处理代码的自动化构建工做(如打包、代码分层、压缩等等)。vue
四、在 network 中,当有一个请求过来时,服务器会新建一个vue实例,渲染(render)出须要显示的页面的html,把获得的页面以字符串的形式返回给客户端。同时把相关的js文件也返回(首次请求时返回vue的runtime、webpack的runtime和app.js等文件,非首次请求返回按需加载的js文件),返回的js文件和单页面应用(SPA)返回的差很少node
app.js:基本就是你实际编写的那个app.vue(.vue或.js),没这个页面跑不起来,该页面应该提供了跟app应用相关的公共方法,脚本里也明确配置了跟路由相关的信息webpack
vendor.js:vue-cli全家桶默认配置里面这个chunk就是将全部从node_modules/里require(import)的依赖都打包到这里,因此这个就是全部node_modules/下的被require(import)的js文件git
manifest.js: 最后一个chunk,被注入了webpackJsonp的定义及异步加载相关的定义(webpack调用CommonsChunkPlugin处理后模块管理的核心,由于是核心,因此要第一个进行加载,否则会报错),该文件肯定是跟路由相关的配置信息,其中明确包含了路由的路径,和版本号,可是暂时不明白为什么前端输出会保留该配置(大概是作一些页面动态切换效果或者是预加载的时候使用,可是页面的预加载已经在ssr 输出的html 已经包含了)es6
而后还有一些 pages_index.js文件,布局 layouts_blog.js文件等:default.js(跟dis/layout/default.js一致,是载入了使用的layout)github
。浏览器接收到这些文件后,经过js文件把静态页面的字符串hydrate成能够交互的应用。和SPA相比,SSR返回的数据就是多了个静态页面(字符串形式)。web
我又一次老生常谈的说了一遍,仍是感受不是很清晰,看来本身的功底仍是不行呀,若是有爱好 nuxt 或者 作过 SSR 的小伙伴,欢迎联系,我们一块儿讨论下,今天呢,接着昨天的工做,把详情页渲染出来吧~~~
通过昨天的首页渲染,你们不知道使用起来怎么样,不只能够配置每一页的 head 信息( TDK head),还能够对总体进行配置,虽然中间引入了 plugins 插件机制,不过也是很好的作了封装,特别是路由这一块,你们是否是发现已经彻底不用配置了,Nuxt.js 依据 pages
目录结构自动生成 vue-router 模块的路由配置,为咱们减小了很大的工做量,今天我们就继续对详情页进行配置。
昨天呢,我们开发了首页,经过地址直接能够访问,可是在开发过程当中,确定会有这样的页面:经过不一样的 id 加载不一样的详情页面,这些页面虽然是一个,可是 URL 地址倒是多个,因此咱们就说这个路由是动态的,还记得我们在第一个项目中的时候,是怎么配置的么?咱们经过页面接收参数来实现动态路由
{ path: "/Content/:id", name: "Content", component: Content },
在 Nuxt.js 里面定义带参数的动态路由,须要建立对应的如下划线做为前缀的 Vue 文件 或 目录。
如下目录结构:
pages/ --| _slug/ -----| comments.vue -----| index.vue --| users/ -----| _id.vue --| index.vue
Nuxt.js 生成对应的路由配置表为:
router: { routes: [ { name: 'index', path: '/', component: 'pages/index.vue' }, { name: 'users-id', path: '/users/:id?', component: 'pages/users/_id.vue' } ] }
你会发现名称为 users-id
的路由路径带有 :id?
参数,表示该路由是可选的。若是你想将它设置为必选的路由,须要在 users/_id
目录内建立一个 index.vue
文件。
一、在 pages 文件夹中,添加 blog 文件夹,而后添加 _id.vue 页面
这个时候,咱们看咱们的临时编译文件 .nuxt 中 router.js 已经动态的增长上了上边添加的路由
return new Router({ mode: 'history', base: '/', linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [ { path: "/blog/:id?", component: _66cb1a63, name: "blog-id" }, { path: "/", component: _70e72bdd, name: "index" } ],
二、编辑 _id.vue 文件,实现数据获取
<template> <div class="post-page"> <h1 class="title">{{data.btitle}}</h1> <p class="createTime">{{data.bCreateTime }}</p> <div v-html="data.bcontent" ></div> </div> </template> <script> import Vue from "vue"; export default { layout: "blog", validate ({ params }) { // 校验文章id是否为数字 return /^\d+$/.test(params.id); }, async asyncData ({ params, error }) { // 获取文章详情 let data = {}; try { data = await Vue.http.get(`blog/${params.id}`); return { data: data }; } catch (e) { //error({ statusCode: 404, message: "出错啦" }); } }, fetch ({ store, params }) {}, data () { return { comments: [] }; }, head () {//设置页面 head 信息 return { title: `${this.data.btitle}`, meta: [ { name: "description", content: this.data.btitle } ] }; }, filters: { timeFormat: function (time) { if (!time) return ""; return time; } }, mounted () {}, components: { } }; </script> //导入样式 <style lang="css"> @import "../../static/vue-blog-sq.css"; </style>
是否是很简单,直接添加页面内容,就能够实现路由渲染,直接就能够访问了,不过这里可能会有一个坑,若是你运气好的话,会碰上,运气很差,就过去了。
三、刷新页面查看结果
苍天呀,不是吧,报错了?!若是你看到这个错误,恭喜你比较幸运,可能你会进一步的了解到 nuxt 是如何渲染的。
四、点击 浏览器后退 ,返回到首页,发现更加崩溃
不只刚刚的详情页不见了,就连咱们的首页数据也出错了!虽然这上面有数据,可是这个是浏览器缓存的,而不是咱们真实的数据,这个时候着急的小伙伴,必定会很着急,稳住,咱们能赢!
这个时候,若是你刷新首页,发现一切正常,不只如何,若是你刷新详情页,数据也能出现,不信你能够试试,那这是为何呢?
缘由就在于咱们刷新页面,或者新窗口打开等等,都是新开了一个服务,咱们的页面为了实现 SEO 先进行的是服务端渲染,讲整个页面的字符串发送过来,而后点击连接去详情页的时候,咱们就开始走客户端渲染了,之因此页面会报错,就是咱们存在跨域的问题。
你可能会问,问什么第一次不存在,由于第一次是服务端渲染呀,服务端是不存在跨域问题的,只有 js 请求才会存在跨域的问题,到这里,经过这个错误你是否是了解到了一点儿,这个错误也是我故意放出来的,就是为了让你们更清楚的了解到 nuxt 是如何进行渲染的。这也能说的通,为何第一次刷新首页有数据,从详情页返回过来,报错的缘由了,由于第二次渲染已经交给客户端了。
解决办法很简单,仍是在咱们 .net core api 中 CORS 跨域配置咱们的端口就行,而后一切正常了。
相信这个时候你对 nuxt 的渲染有了一点理解了吧,若是还不是很清晰,请往下看
SSR 用经过同构的方法解决了上面问题。咱们先说一下 SSR 的具体表现,好比咱们如今有一个列表页,列表中每一行对应一个详情页,那么若是直接用浏览器访问列表页时,服务器返回数据和 html 融合后的页面,浏览器拿到页面直接渲染,这就省去了先请求 js 再由 js 发起数据请求的过程,页面渲染的同时请求js,js加载完成后绑定事件;从列表页中点击某一条到详情页的时候,和普通的全栈 Ajax 同样,先请求 js 再由 js 发起数据请求,而后填充数据渲染页面。若是将详情页的连接复制出来,直接在新浏览中访问,那么详情页会直接返回数据和 html 融合后的页面(服务端渲染),渲染的同时请求详情页 js,最后再绑定事件。这个“服务器端拼接 html 和 html 是由一样的页面和组件完成的,这种先后端采用一样的结构在不一样的环境中产出一样的 html 的方案称之为“同构”。
为了解决某些问题(好比SEO、提高渲染速度等)vue 提供了2个方法在服务端生成一个HTML文本格式的字符串。在获得了这个HTML格式的字符串以后,一般会将其组装成一个页面直接返回给用户的浏览器。
到这里,服务端的活已经干完了,而后就是浏览器这边干活。
浏览器拿到HTML文本后,马上进行渲染将内容呈现给用户。而后加载页面所需的 .js 文件,而后执行 JavaScript 脚本,而后开始初始化 vue 组件
到这里问题就来了。vue 初始化组件后会执行组件内全部 render () 方法,而后生成虚拟DOM的树形结构,而后在适当的时候将虚拟dom写到浏览器的真实 dom 中。由于 vue 老是根据虚拟 dom 来生成真实dom,因此最后会把服务器端渲染好的HTML所有替换掉。
上面这个事情说不是问题确实也不是问题,无非就是用户看到页面而后“闪现”一下。说是问题还真是个问题,产品会拿着这毛病从用户体验的角度在各类场合和你死磕半个月。磕累了你索性把服务端渲染关了,而后运营又拿着SEO的问题准备和你开始撕逼了。
为了解决这些问题,他们在 .renderToString(element) 方法中提供了一个 checksum 机制。先后端同构就是保证前端和后端的dom结构一致,不会发生重复渲染。
简单的说就是 vue 在浏览器内存中第一次生成的虚拟 dom 树。切记是虚拟 dom ,而不是浏览器的dom。
了解 vue 的应该知道,全部 vue组件都有一个 render() 方法(若是使用function方式编写的组件会把function里的全部代码都塞到 render() 方法中去)。当 render( element, container, [callback] )方法执行时,会执行如下步骤:
1. 全部组件的会先进行初始化(es6执行构造函数)。
2. 全部组件的 render () 方法会被调用一次,完成这个过程后会获得一颗虚拟的 dom 树。
3. vue 会将虚拟dom转换成浏览器dom,完成后调用组件的 componentDidMount() 方法告诉你已经装载到浏览器上了。
在上面这个过程成中,步骤2完成后即为完成 vue 的首屏渲染。结合 checksum 机制步骤3有可能不会执行。
当组件状态发生变动时( setState() 生命周期函数被调用)或者 父组件渲染时(父组件的 render() 方法被调用),当前组件的 render() 方法都会被执行,都有可能会致使虚拟dom变动,可是这些变动和首屏渲染没任何关系了。
一、在咱们的首页中,首次加载,在 network 中,查看咱们都加载了那些文件
这些文件我们在文章顶部都讲到了,这里说下 初始页面,它是直接将 html 返回给咱们的前端渲染,这个很好理解
二、点击到详情页
咱们发现这个咱们的网络请求,并无继续打包 build 走服务端渲染,而是仅仅请求了一个接口,返回了 json 数据,从这里你们应该就能看的处理,这就是所谓的双端渲染模式。
好啦,今天就暂时说到这里了,经过详情页的添加,你们会切身体会到 nuxt 的渲染模式,是如何在服务端和客户端之间来回切换渲染的,这三篇文章你们要多看看,才能了解其中的内涵,加油鸭~~