内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

服务器端渲染与Nuxt.js

2018-08-04 18:20 出处:清屏网 人气: 评论(0

前段时间在知乎上看到一篇提问,说的是为什么现在又开始流行服务器端渲染html了。整理了网上一些评论,结合自己的想法,整理出了一段前端发展史。

早在1989年,HTML的诞生是一个物理学家为了方便学术文档的分享而创造,这个也是前端起始的时间。后来,CSS和Javascript加入前端行列,用来渲染页面样式和处理页面动效逻辑,前端三剑客成立。刚开始的前端程序员,其实就是做切图写样式(CSS)和做页面特效(JS)等一切基础的工作,处于程序员鄙视链的底层。

随着互联网发展与技术进步,静态页面已经远不能满足产品需求,页面上要根据逻辑产生动态的数据,这时,便迎来PHP,JSP等为代表的web1.0时代。此时的服务器渲染,是以“文档”为核心思想。服务器端的逻辑是把HTML,CSS和JS当做一个静态文件,对“文档”而言不存在“指令”和“数据”的区别,一切都是数据。所以我们可以看到服务器渲染,GET就是请求一个文件,而web 1.0时代的诸多服务端框架最基础的组件之一就是文档模版,比如asp, JSP之类,核心设计理念就是HTML文件里放占位符然后由服务端逻辑替换成实际数据后一股脑返回。很多中小型项目,不分前端后端,大家都是web开发工程师,按现在的说法叫全栈工程师。而在现在来看,这样的模式是存在很多问题的,拿jsp举例,动态资源和静态资源完全耦合,服务器压力大,而且一旦出现状况,前后台一起玩完,用户体验极差;jsp必须要在支持java的web服务器里运行,性能提不上来;如果jsp中内容很多,页面响应会很慢……

1998年,IE5.0引入XMLHttpRequest技术,实现了异步调用服务器的功能,2005年,Google在它著名的交互应用程序中使用了ajax异步通讯,web前端引来2.0革命。之后W3C发布XMLHttpRequest标准,为之后的ajax爆发提供技术基础。

2006年,JQuery工具库发布,一经出世凭借其简单易容的特性和解决浏览器兼容性的能力风靡全球。

2010年,Backbone诞生,RequireJS第一个版本发布,前端的模块化开发时代正式来临了。而后,随着前端MVC的兴起,SPA(Single Page Application 单页面应用)开始变成一种项目开发的潮流,前后端分工非常清晰。前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。此时前后端分离的运动在各大公司间兴起,前端自立门户,独立发展。前端程序员们翻身的机会来了。

然而此时,很多本不该被做成SPA的也被做成了SPA。但是,SPA应用存在种种问题,比如SEO,比如首屏加载速度,这让前端开发人员优化愁白了头。

随着Node.js的兴起,Javascript开始有能力运行在服务器端,这意味这有一种新的研发模式:Front-end UI layer 处理浏览器层的展现逻辑,Back-end UI layer 处理路由、模板、数据获取、cookie 等。通过 Node,Web Server 层也是 JavaScript 代码,这意味着部分代码可前后复用,需要 SEO 的场景可以在服务端同步渲染,由于异步请求太多导致的性能问题也可以通过服务端来缓解。前一种模式的不足,通过这种模式几乎都能完美解决掉。

Web 2.0时代最大的思想革命本质不是前后端分离,而是把网页当作独立的应用程序(app)。前后端分离只是实现这一新架构的必然结果。对程序而言指令和数据是分离的。HTTP GET拿到的不是渲染后的网页,而是一个由html和Javascript组成的app, 这个app以浏览器为虚拟机。装载和显示数据是app启动之后的运行逻辑。传统上app叫什么?叫Client,也就是前端。于是前后端就这么分离了,浏览器变成了app的运行环境,后端蜕化成了单纯的业务逻辑和数据接口。写Javascript不再是给网页添特效的小伎俩,而是正经的和写桌面应用程序一样的工程。于是我们看到了前端工程化,编译(转译),各种MVC/MVVM框架,依赖工具,等等。

为什么要用服务器端渲染?

使用服务器端渲染,最主要的问题,其实就是为了解决SEO的问题。如果SPA应用也有良好的SEO,就不用服务器端渲染这么麻烦了。当然服务器端渲染能解决的首屏加载速度的问题也是原因之一。那么,SEO是什么呢?

SEO(Search Engine Optimization),搜索引擎优化。比如谷歌、百度需要抓取你所发布的网站信息来进行自然排序,是通过爬虫进行的。

来看两段代码:

上面是之前很早前写过的两段掘金文章的爬虫代码(写的有点low),大概思路就是使用superagent发送http请求,把整个页面(文档对象)爬下来,包括head, body等,然后用cheerio进行解析,然后抓取页面节点元素以及关键信息。可能你觉得,这个简单,我页面上信息都是通过ajax请求到然后插入到dom元素中的。注意,爬虫爬到的页面并没有发送ajax请求,就是一个初始化的纯静态页面,如果你用的是spa应用,那可能body中除了一个id为app的节点,什么都没有。所以,我们需要让页面在服务器端就已经被渲染完成,传给客户端的时候已经是一个具有数据信息的静态html文档。当然,现在也有针对SPA应用进行的SEO优化方案,这个不在本文讨论范围之内。

以下总的列举服务器渲染的一些优缺点:

优点

  • 有利于SEO。
  • 首屏加载速度快。因为SPA引用需要在首屏获取所有资源,而服务器端渲染直接拿了成品展示出来就行了。
  • 无需占用客户端资源。解析模板工作交给服务器完成,对于客户端资源占用更少,尤其是移动端,也可以更省电。

缺点

  • 占用服务器资源。服务器端完成html模板解析,如果请求较多,会对服务器造成一定的访问压力。而如果是前端渲染,就是把这些压力分摊给了前端。
  • 不利于前后端分离。

VUE SSR

通用(也称同构)的JavaScript已经成为JavaScript社区很常用的一个术语。通用的JavaScript用来形容可以在客户端执行,也可在服务端执行的Javascript代码。 在VUE的官方文档上是这么描述的:

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。

前半句好理解,就是说你可以在服务器(后端)环境中,使用vue.js来构建组件和页面,然后将渲染好的静态html字符串传给客户端展示。后半句感觉句子不太通顺,英文版是这样的:

“Finally "hydrate" the static markup into a fully interactive app on the client.”

大概意思就是说,把应用传给客户端以后,由于一些静态标记,客户端也会具备同样的交互(就是MVVM双向数据绑定)。

官网给出的这张构建步骤图也可以看出,对于客户端应用程序和服务器应用程序,我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。 本文不过多深入 Vue SSR

源生的内容,Vue官方推荐了一个优秀的社区项目Nuxt.js,它为Vue的服务端渲染提供了非常良好的开发体验,我们将主要来讨论一下它。

Nuxt.js

Nuxt.js 是什么?

构建服务端渲染的JavaScript程序多少有些无趣,在开始编码之前,需要大量的基础配置。因此,解决vue.js服务端渲染问题的Nuxt.js产生了。 Nuxt.js 是一个基于 Vue.js 的通用应用框架。预设了服务器端渲染所需的各种配置,如异步数据,中间件,路由,只要遵循其中的规则就能轻松实现SSR。。它好比是 Angular Universal 之于 Angular, Next.js 之于 React。 通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。

Nuxt.js 能做什么

  • 无需再为了路由划分而烦恼,只需要按照对应的文件夹层级创建 .vue 文件就行
  • 无需考虑数据传输问题,nuxt 会在模板输出之前异步请求数据(需要引入 axios 库),而且对 vuex 有进一步的封装
  • 内置了 webpack,省去了配置 webpack 的步骤,nuxt 会根据配置打包对应的文件

Nuxt.js 的安装与运行

在安装vue-cli的情况下,快速生成一个nuxt项目的命令如下:

$ vue init nuxt-community/starter-template <project-name>
复制代码

进入项目目录后

$ npm install
复制代码

然后启动项目

$ npm run dev
复制代码

这样项目就能正常运行在 http://localhost:3000

Nuxt.js 的实践介绍

这里就不详细介绍nuxt.js的一些用法和API了,可以直接看官网的教程:zh.nuxtjs.org/guide 。

我自己做了一个nuxt.js的简单demo(极其简单), Github地址 ,这里就我自己的的一些体验,对比spa应用,来聊聊这个框架。

1. Nuxt.js的目录结构

nuxt是采用vue-cli来创建的模板,相比常规的vue模板,他们具备非常重要的一点:方便。nuxt.js同样已经将各种项目所需的webpack配置替我们打理好了,开箱即用,基本不需要作什么改动。而且即使需要自定义一些配置,修改起来也非常简单。我们来把它的目录结构和SPA应用作一个对比。

从上图可以看出,nuxt似乎把src文件夹中的很多内容给放到了外边,少了一些文件,多了几个没见过的文件夹。这些文件夹各有各的用处。

  • layouts用于放置页面布局。
  • middleware用于放置一些中间件,我们在页面组件中可以引用这些中间件,在执行页面逻辑的时候会先执行其中的逻辑。
  • pages就是放置我们的所有页面组件啦,但是,与spa应用不同的是,nuxt里的page会根据文件和文件夹结构生成对应的路由,打个比方,我page文件夹下的目录结构如下
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'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}
复制代码
index.js

2. Nuxt.js的页面组件

从上图的Nuxt.js和SPA的.vue单页文件对比,,除了一般的.vue单页文件中的常规实例属性外(比如data, methods,props等),Nuxt.js还提供了很多方便又有意思的属性,这也是Nuxt.js最大的特点之一。

  1. 在nuxt内部的整个执行流程中,最先经过的是状态管理中 actions 中的nuxtServerInit函数,这个我们等会再说。
  2. 然后会经过 middleware 里的中间件函数,此时,还没有进行数据获取和页面渲染,所以我们可以在中间件函数中执行一些进入路由前的逻辑,比如用户权限判断。
  3. 之后开始获取页面数据,asyncData和data的结果基本相同,我们可以直接调用server的接口,比如理由axios发送http请求获取页面所需的原始数据,然后以对象的形式return出去,此时,Vue对象还没有实例化,所以asyncData里无法调用到 this
  4. fetch里主要用作填充状态树(store)数据。
  5. 这些全部做完以后,开始实例化Vue对象,这里的逻辑和单页应用是一样的,在组装好整个页面应用之后,nuxt.js会将这个应用返回至前端。注意,这里返回的不是单纯的页面,而是应用。此时的页面局部spa应用的一些性质,比如数据监听双向绑定。
  6. 页面来到前端后,开始执行mount的相关逻辑。
    除了应用的执行流程外,再看看页面渲染的模块。
  • head 部分可以自定义当前页面的头部信息,比如title, meta之类的。当然,如果需要定义全局head可以在 nuxt.config.js 中配置。
  • layout 部分可以自定义页面布局,很多页面公用的静态头、尾部分可以统一定义按需引用。
  • scrollToTop 用于页面跳转时将页面滚动置顶。
  • transition 用于页面间跳转的过渡动画。

3. Vuex状态树

整个demo做下来,目前让我印象最深的就是状态树,它和SPA应用还是有一定区别的。

当时我需要完成的需求是, 保存用户信息,并在任何页面可以使用它,如果非登录页没有获取到用户信息,跳转回登录页

起初,我的设计思路是,在用户登录成功后,调用后台接口获取该用户所有信息,并且存在store中。流程图如下:

按照SPA应用的情况,store里的数据应该在页面组件中都是共享的。但是,发现Nuxt中一旦页面跳转,整个Vuex状态树会重置,原来存下的用户信息也没有了。由此可以推测,不同路由下的页面是一个独立的应用,它们并不会共享state中的数据。

这时我想到了本地会话存储localStorage,只要把原流程中从store存取的逻辑改为从localStorage的逻辑即可。这种方式是可行的,但是这样一来,Vuex感觉存在感就不强了(此事必有蹊跷),并且就没法用server层来控制会话的过期等逻辑。

后来发现,服务器端渲染的vuex中的action中提供了一个方法: nuxtServerInit 。Nuxt.js 调用它的时候会将页面的上下文对象作为第2个参数传给它,上下文对象可以拿到 req 请求对象,那么就存在这么一种逻辑。我可以将用户信息存储在服务器session中,然后通过 req.session.user

来访问当前登录的用户。将用户登录信息传给客户端的状态树,代码如下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}
复制代码

这样在配合middleware中间件,就可以完成用户信息获取和会话控制,流程如下:

4. Nuxt.js的其它相关

其它还涉及的一些内容,其实看看官网教程,看看官网示例都能搞定,教程还是非常易懂的。

总结

使用Vue,React等服务器渲染,并不是走以前模板式渲染的老路。它已经跨越历史,朝着更优秀的方面发展。 而Nuxt.js,还是一个非常年轻的框架(现在官网才是0.10.7版本),目前也有很多待改进的问题,但它的出现为 Vue.js 开发者搭建服务端渲染项目提供了巨大的便利。听说Nuxt.js 2.0 即将来临,期待版本发布后,能给我们带来更多实用的新功能。


分享给小伙伴们:
本文标签: 服务器Nuxt.js

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号