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

_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 {
  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 | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array {
  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
Vue 源码解析(六)之 vm._render_第1张图片
  • _createElement 创建了vnode
Vue 源码解析(六)之 vm._render_第2张图片
  • 因此 _render 最终返回了一个 vnode
Vue 源码解析(六)之 vm._render_第3张图片

你可能感兴趣的:(Vue 源码解析(六)之 vm._render)