遇见Vue.js

  • 关键词:Vue.js – 读书笔记

十七、Vue.js 2.0

部分API变更、模板更加灵活之外,高性能的Virtual DOM和流式服务端渲染功能。

服务端渲染

新增renderToString(component, done)方法

它的功能是在服务端把Vue组件component渲染成DOM字符串,我们可以在done的回调函数中拿到渲染好的DOM字符串。

新增renderToStream(component)方法

它的功能是返回一个渲染流,是一个可读的stream,可以直接pipe到HTTP Response中。当APP非常复杂时也不会阻塞服务器的event loop,能够确保服务端的响应度,也能让用户更快地获得渲染内容。

Virtual DOM

DOM操作的复杂度在提升。

MVC、MVVM框架的引入,从代码组织方式来降低维护这种复杂应用的难度。本质上并没有减少所维护的状态,也没有减少页面的DOM操作。

了解到DOM的一些弊端,提出了Virtual DOM的概念,它是一种虚拟DOM技术,本质上是基于JavaScript实现的。

相对于DOM对象,JavaScript对象更简单,处理速度更快,DOM树的结构、属性信息都可以很容易地用JavaScript对象来表示。

    var element = {
        tagName: 'ul',
        props: {
            id: 'list'
        },
        children: [
            {tagName: 'li', props: {class: 'item'}, children: {"DDFE 小Z"}}
            {tagName: 'li', props: {class: 'item'}, children: {"DDFE 小H"}}
            {tagName: 'li', props: {class: 'item'}, children: {"DDFE 小S"}}
        ]
    }

Virtual DOM核心思想:

我们通过JavaScript对象表示的树结构来构建一颗真正的DOM树,当数据状态发生变化时,可以直接修改这个JavaScript对象,接着对比修改后的JavaScript对象,记录下需要对页面做的DOM操作,然后将其应用到真正的DOM树,实现视图的更新。

Virtual DOM在Vue.js 2.0中实现

1.创建VNode对象模拟DOM树(每一个原生DOM元素或者Vue组件都对应一个VNode对象)

image

数据结构 属性 说明
VNode tag,text,elm,data,parent,children 两种生成方式:一种是由普通DOM元素生成;另一种是由Vue组件生成
VNodeComponentOptions Ctor,propData,listeners,parent,children,tag 用来描述通过Vue组件生成VNode对象的一些组件相关参数
VNodeData slot,ref,staticClass,style,class,props,attrs,transition,directives 用来描述VNode包含的一些节点数据
VNodeDirective name,value,oldValue,arg,modifiers 用来描述VNode存储的指令数据

VNode是在Vue中生成过程

image

VNode生成最关键的点是通过call render function, render方法在Vue.js 2.0中有两种生成方式:

第一种是直接在Vue对象的option中添加render字段;

第二种是像Vue.js 1.x版本那样写一个模板或者指定一个el根元素,它会首先转换成模板,经过HTML语法解析器生成一个ast抽象语法树,对语法树做优化,然后把语法树转换成代码片段,最后通过代码片段生成function添加到option的render字段中。

  • ast语法树优化过程(两件事情):
    • 会检测出静态的class名和attributes,这样它们在初始化渲染之后就永远都不会再被对比了
    • 会检测出最大的静态子树(就是不需要动态性的子树)并且从渲染函数中萃取出来。这样在每次重渲染时,它就会直接重用完全相同的VNode,同时跳过比对。

看一下编译后的渲染函数:

    <div id = "app">
        <h1></h1>
    </div>
    <style>
        new Vue({
            el: "#app",
            data: {
                who: "DDFE"
            }
        });
    </style>
    //我们在vue对象中指定了el根元素,经过一系列的编译操作后,最终生成render方法
    (function(){
        with(this){
            return _h(_e('div', {staticAttrs: {"id": "app"}}), [_h(_e('h1'), [("hello" + _s(who))])])
        }
    })
    //render方法使用了with方法包裹代码块,with(this)中的属性和方法相当于通过this来调用,这样写是为了减少代码量
    //this指向Vue对象实例,_h和_e方法见源码(分别是renderElementWithChildren,renderElement)

renderElementWithChildren是给一个VNode对象添加若干子VNode。

renderElement是创建一个VNode对象,如果是一个reserved tag,则会创建普通的DOM VNode对象;如果是一个component tag,则会创建Component VNode对象,它的VNodeCompontentOptions不为null。

2.VNode patch生成DOM

在Vue.js 2.0中,VNode转换成真正的DOM是通过patch(oldVNode, vnode, hydrating)方法实现的。

patch方法支持3个参数:

oldVnode是一个真实的DOM或者一个VNode对象,表示当前的VNode。

vnode是VNode对象类型,它表示待替换的VNode。

hydrating是bool类型,它表示是否直接使用服务端渲染的DOM元素。

patch方法允许逻辑图

image

patch方法的运行逻辑看上去比较复杂,其中有两个方法createElm和patchVnode是生产DOM的关键。

createElm(vnode, insertedVnodeQueue):

该方法会根据vnode的数据结构创建真实的DOM节点,如果vnode有children,则会遍历这些子节点,递归调用createElm方法。InsertedVnodeQueue是记录子节点创建顺序的队列,每创建一个DOM元素就会往这个队列中插入当前的VNode。当整个VNode对象全部转换成为真实的DOM树时,会依次调用这个队列中VNode hook的insert方法。

patchVnode(oldVnode, vnode, insertedVnodeQueue):

该方法会通过比较新旧VNode节点,根据不同的状态对DOM做合理的更新操作(添加、移动、删除等),整个过程还会依次调用prepatch、update、postpatch等钩子函数。在编译阶段生成的一些静态子树,在这个过程中由于不会改变而直接跳过对比。动态子树在比较过程中比较核心的部分就是当新旧VNode同时存在children,通过updateChildren方法对子节点做更新。

updateChildren,它主要通过while循环一遍遍对比两棵树的子节点来更新DOM。

初始状态

image

step1

image

step2

image

step3

image

更新完成

image

服务端渲染技术

Vue.js 2.0提供了服务端渲染技术,服务端渲染比起客户端渲染页面,有以下佳钓尼优势:

1.首屏渲染速度更快

客户端渲染的一个缺点是,用户第一次访问页面,如果浏览器没有缓存,需要先从服务端下载JS。这个过程通过JS操作动态添加DOM并渲染页面,时间较长。

服务端渲染则是,用户第一次访问浏览器可以直接解析HTML文档并渲染页面。

2.SEO

服务器端渲染可以让搜索引擎更容易读取页面的meta信息,以及其他SEO相关信息,大大增加了网站在搜索引擎中的可见度。

3.减少HTTP请求

服务器渲染可以把一些动态数据在首次渲染时同步输出到页面;而客户端渲染需要通过AJAX等手段异步获取这些数据,这样就相当于多了一次HTTP请求。

普通服务端渲染

Vue.js 2.0提供了renderToString接口,在服务器把Vue组件渲染成模板字符串。

在Node.js环境中,主要依赖:

vue.common.js,是Vue运行时代码,不包括编译部分

vue-server-render,对外提供createRenderer方法,renderToString是createRenderer方法返回值的一个属性。

渲染完成后的回调函数支持两个参数,即err和result。

renderToString方法支持传入Vue实例component和渲染完成后的回调函数done。它定义了result变量,同时定义了write方法,最后执行render方法。

render方法的功能是把component转换成模板字符串str,写入write方法中。write方法不断拼接模板字符串,用result变量做存储,然后调用next方法。当component通过render渲染模板字符串完毕后,执行done,传入result。

render方法实际上是执行了renderNode方法,并把compontent._render()方法生成的VNode对象作为参数传入。

renderNode方法

image

renderElement方法主要是把VNode对象渲染成DOM元素

image

流式服务端渲染

普通服务端渲染的痛点:由于渲染是同步过程,所以如果这个App很复杂的话,它可能会阻塞服务器的event loop,同步服务器渲染在优化不当时甚至会给客户端获得内容的速度带来负面影响。

renderToStream接口,在渲染组件时返回一个可读的stream,可以直接pipe到HTTP Response中。

流式渲染能够确保服务器响应度,也能让用户更快地获得渲染内容。

image

renderToStream会把Vue实例渲染成一个可读的stream。如果代码运行在Express框架中,则可以通过app.use方法创建middleware,然后直接把stream pipe到res中,这样客户端就能很快地获得渲染内容了。

renderToStream继承了node中的可读流stream.Readable,它是一个自定义的可读流。所有可读流的实现都必须提供一个_read方法(_开头的,表明它是一个内部方法,并且不应该被用户程序直接调用)从底层资源抓取数据。

可读流通过push方法向队列中插入一些数据,而且至少需要调用一次push(chunk)方法,_read方法才会再次被调用。当push方法传入null参数时,它会触发数据结束信号(EOF)。

RenderStream类 说明
Constructor(render) image
Write(text,next) image
end image
_read(n) image
PushBySize(n) image
tryRender image
tryNext image