美文网首页
Vue.js 源码学习笔记3 数据驱动(中)

Vue.js 源码学习笔记3 数据驱动(中)

作者: 俺是种瓜低 | 来源:发表于2018-11-15 16:44 被阅读0次

render

Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 node( DOM  当初看某个文档 还以为是node.js 一脸懵逼啊)。它的定义在 src/core/instance/render.js 文件中

// Vue 的 _render 方法是实例的一个私有方法,它用来把实例渲染成一个虚拟 Node

  // 这段代码最关键的是 render 方法的调用,我们在平时的开发工作中手写 render 方法的场景比较少,而写的比较多的是 template 模板,在之前的 mounted 方法的实现中,

  // 会把 template 编译成 render 方法,但这个编译过程是非常复杂的

  Vue.prototype._render = function (): VNode {

    const vm: Component = this

    const { render, _parentVnode } = vm.$options

    // reset _rendered flag on slots for duplicate slot check

    if (process.env.NODE_ENV !== 'production') {

      for (const key in vm.$slots) {

        // $flow-disable-line

        vm.$slots[key]._rendered = false

      }

    }

    if (_parentVnode) {

      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject

    }

    // set parent vnode. this allows render functions to have access

    // to the data on the placeholder node.

    vm.$vnode = _parentVnode

    // render self

    let vnode

    try {

      vnode = render.call(vm._renderProxy, vm.$createElement)

    } catch (e) {

      handleError(e, vm, `render`)

      // return error render result,

      // or previous vnode to prevent render error causing blank component

      /* istanbul ignore else */

      if (process.env.NODE_ENV !== 'production') {

        if (vm.$options.renderError) {

          try {

            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)

          } catch (e) {

            handleError(e, vm, `renderError`)

            vnode = vm._vnode

          }

        } else {

          vnode = vm._vnode

        }

      } else {

        vnode = vm._vnode

      }

    }

    // return empty vnode in case the render function errored out

    if (!(vnode instanceof VNode)) {

      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {

        warn(

          'Multiple root nodes returned from render function. Render function ' +

          'should return a single root node.',

          vm

        )

      }

      vnode = createEmptyVNode()

    }

    // set parent

    vnode.parent = _parentVnode

    return vnode

  }

// 这段代码最关键的是 render 方法的调用,我们在平时的开发工作中手写 render 方法的场景比较少,

  // 而写的比较多的是 template 模板,在之前的 mounted 方法的实现中,会把 template 编译成 render 方法,但这个编译过程是非常复杂的

render 的 createElement

在 Vue 的官方文档中介绍了 render 函数的第一个参数是 createElement,那么结合之前的例子:

  {{ message }}

</div>

相当于我们编写如下 render 函数:

render: function (createElement) {

  return createElement('div', {

     attrs: {  

        id: 'app'

      },       

  }, this.message)

}

// 实际上,vm.$createElement 方法定义是在执行 initRender 方法的时候,可以看到除了 vm.$createElement 方法,

// 还有一个 vm._c 方法,它是被模板编译成的 render 函数使用,而 vm.$createElement 是用户手写 render 方法使用的,

// 这俩个方法支持的参数相同,并且内部都调用了 createElement 方法。

export function initRender (vm: Component) {

  vm._vnode = null // the root of the child tree

  vm._staticTrees = null // v-once cached trees

  const options = vm.$options

  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree

  const renderContext = parentVnode && parentVnode.context

  vm.$slots = resolveSlots(options._renderChildren, renderContext)

  vm.$scopedSlots = emptyObject

  // bind the createElement fn to this instance

  // so that we get proper render context inside it.

  // args order: tag, data, children, normalizationType, alwaysNormalize

  // internal version is used by render functions compiled from templates

  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

  // normalization is always applied for the public version, used in

  // user-written render functions.

  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.

  // they need to be reactive so that HOCs using them are always updated

  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */

  if (process.env.NODE_ENV !== 'production') {

    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {

      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)

    }, true)

    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {

      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)

    }, true)

  } else {

    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)

    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)

  }

}

vm._render 最终是通过执行 createElement 方法并返回的是 vnode,它是一个虚拟 Node。Vue 2.0 相比 Vue 1.0 最大的升级就是利用了 Virtual DOM。因此在分析 createElement 的实现前,我们先了解一下 Virtual DOM 的概念。


Virtual DOM


Virtual DOM 大部分人都不会陌生,它产生的前提是浏览器中的 DOM 是很“昂贵"的 我们可以简单的把一个简单的 div 元素的属性都打印出来

真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。当我们频繁的去做 DOM 更新,会产生一定的性能问题。

而 Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去描述,它是定义在 src/core/vdom/vnode.js 中的。

实际上 Vue.js 中 Virtual DOM 是借鉴了一个开源库 snabbdom 的实现,然后加入了一些 Vue.js 特色的东西。

export default class VNode {

  tag: string | void;

  data: VNodeData | void;

  children: ?Array<VNode>;

  text: string | void;

  elm: Node | void;

  ns: string | void;

  context: Component | void; // rendered in this component's scope

  key: string | number | void;

  componentOptions: VNodeComponentOptions | void;

  componentInstance: Component | void; // component instance

  parent: VNode | void; // component placeholder node

  // strictly internal

  raw: boolean; // contains raw HTML? (server only)

  isStatic: boolean; // hoisted static node

  isRootInsert: boolean; // necessary for enter transition check

  isComment: boolean; // empty comment placeholder?

  isCloned: boolean; // is a cloned node?

  isOnce: boolean; // is a v-once node?

  asyncFactory: Function | void; // async component factory function

  asyncMeta: Object | void;

  isAsyncPlaceholder: boolean;

  ssrContext: Object | void;

  fnContext: Component | void; // real context vm for functional nodes

  fnOptions: ?ComponentOptions; // for SSR caching

  fnScopeId: ?string; // functional scope id support

  constructor (

    tag?: string,

    data?: VNodeData,

    children?: ?Array<VNode>,

    text?: string,

    elm?: Node,

    context?: Component,

    componentOptions?: VNodeComponentOptions,

    asyncFactory?: Function

  ) {

    this.tag = tag

    this.data = data

    this.children = children

    this.text = text

    this.elm = elm

    this.ns = undefined

    this.context = context

    this.fnContext = undefined

    this.fnOptions = undefined

    this.fnScopeId = undefined

    this.key = data && data.key

    this.componentOptions = componentOptions

    this.componentInstance = undefined

    this.parent = undefined

    this.raw = false

    this.isStatic = false

    this.isRootInsert = true

    this.isComment = false

    this.isCloned = false

    this.isOnce = false

    this.asyncFactory = asyncFactory

    this.asyncMeta = undefined

    this.isAsyncPlaceholder = false

  }

  // DEPRECATED: alias for componentInstance for backwards compat.

  /* istanbul ignore next */

  get child (): Component | void {

    return this.componentInstance

  }

}

其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。

Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的create、diff、patch 等过程。那么在 Vue.js 中,VNode 的create 是通过之前提到的 createElement 方法创建的。

相关文章

网友评论

      本文标题:Vue.js 源码学习笔记3 数据驱动(中)

      本文链接:https://www.haomeiwen.com/subject/cqidtqtx.html