美文网首页
Vue 源码解析(六)之 vm._render

Vue 源码解析(六)之 vm._render

作者: Love小六六 | 来源:发表于2019-08-22 00:17 被阅读0次

    _render函数

    • 上一讲我们分析到调用了vm._update(vm._render(), hydrating),那么这一讲我们就先分析 vm._render() 做了什么
    • 首先 _render 方法是在哪里定义的呢?在core/instance/render.js
    Vue.prototype._render = function (): VNode {
        const vm: Component = this
        const { render, _parentVnode } = vm.$options
        
        if (_parentVnode) {
          vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
          )
        }
        
        
        vm.$vnode = _parentVnode
            
        let vnode
        try {
          currentRenderingInstance = vm
          vnode = render.call(vm._renderProxy, vm.$createElement)
        } catch (e) {
          handleError(e, vm, `render`)
          
          vnode = vm._vnode
        } finally {
          currentRenderingInstance = null
        }
        if (Array.isArray(vnode) && vnode.length === 1) {
          vnode = vnode[0]
        }
            
        if (!(vnode instanceof VNode)) {
          vnode = createEmptyVNode()
        }
        vnode.parent = _parentVnode
        return vnode
    }
    
    • 我们可以看到const { render, _parentVnode } = vm.$options先从 $options 中拿到了render

    • 然后执行了 render,并传参 vm.$createElement, 从而获取到 vnode 并返回

    // vm._renderProxy就是vm,忽略不分析
    vnode = render.call(vm._renderProxy, vm.$createElement)
    

    vnode

    • dom元素是非常庞大的,我们频繁去做dom更新就会产生一定的性能问题
    • Vnode 就是用原生的 js 对象去描述 DOM 节点,它借鉴了开源库snabbdom ,在 flow 文件夹下可以看到 VNode 的定义,,对 vnode 定义了一些关键属性如标签名、数据、子节点等,用来映射到真实 DOM 的渲染,因此轻量简单
    • Vnode 映射到真实 DOM 需要经历创建 diff patch等过程
    • Vnode 是如何创建的呢? 就是通过我们之前分析的 vm.$createElement 函数

    vm.$createElement 是什么

    • 在 function Vue 阶段做了各种 mixin , 其中有 initRender, initRender 中包括这样一段代码
    vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
    
    • 也就是说我们的 vm.$createElement 实际上是去执行了 createElement 操作

    createElement

    • core/vdom/create-element.js
    export function createElement (
      context: Component, // vm 实例
      tag: any, // 标签
      data: any, // 数据
      children: any, // 子节点,从而构建出 vnode tree
      normalizationType: any,
      alwaysNormalize: boolean
    ): VNode | Array<VNode> {
      if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children
        // 没有 data 时参数前移
        children = data
        data = undefined
      }
      if (isTrue(alwaysNormalize)) {
        normalizationType = ALWAYS_NORMALIZE
      }
      return _createElement(context, tag, data, children, normalizationType)
    }
    
    • 我们发现它实际上是对参数做了一层封装,然后调用 _createElement 方法

    _createElement

    • 我把这部分的代码精简了一下,保留了核心的部分
    export function _createElement (
      context: Component,
      tag?: string | Class<Component> | Function | Object,
      data?: VNodeData,
      children?: any,
      normalizationType?: number
    ): VNode | Array<VNode> {
      if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
      } else if (normalizationType === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
      }
      let vnode, ns
      if (typeof tag === 'string') {
        let Ctor
        ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
        if (config.isReservedTag(tag)) {
          vnode = new VNode(
            config.parsePlatformTagName(tag), data, children,
            undefined, undefined, context
          )
        } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
          vnode = createComponent(Ctor, data, context, children, tag)
        } else {
          vnode = new VNode(
            tag, data, children,
            undefined, undefined, context
          )
        }
      } else {
        vnode = createComponent(tag, data, context, children)
      }
      if (Array.isArray(vnode)) {
        return vnode
      } else if (isDef(vnode)) {
        if (isDef(ns)) applyNS(vnode, ns)
        if (isDef(data)) registerDeepBindings(data)
        return vnode
      } else {
        return createEmptyVNode()
      }
    }
    
    • 我们会发现这个函数先对 children 做了 normalize, 因为children 可能是数组类型,normalize方法,normalizeArrayChildren 主要的逻辑是遍历 children,获得单个节点 c, 然后判断 c 的类型,如果是数组类型,则递归调用 normalizeArrayChildren,如果是基础类型,则通过 createTextVNode 转化为 VNode 类型(其中做了优化,两个连续的 text 节点会合并成一个 text 节点),变成了一个一维数组
    • 我们现在 demo 的 tag 是 string 类型,因此会走到 vnode = new Vnode()从而生成一个 vnode

    demo 调试

    • 调用 _createElement
    • _createElement 创建了vnode
    • 因此 _render 最终返回了一个 vnode

    相关文章

      网友评论

          本文标题:Vue 源码解析(六)之 vm._render

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