Vue源码系列 -- 数据驱动流程分析(4)

206 阅读1分钟

createElement 方法

在render函数中返回了VNode,而VNode是有createElement方法创建的
数据驱动流程分析(3)vnode = render.call(vm._renderProxy, vm.$createElement),可以看到最终执行的是vm.$createElement方法

// src/croe/instance/render
export function initRender (vm: Component) {
  // 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)
}

可以看到vm.$createElement是在initRender函数中定义的,除此之外,还定义了vm._c这个方法

  • vm._c 是被模板编译生成的render函数使用
  • vm.$createElement 是用户手写render函数使用 里面参数都是一致的且都调用了createElement()方法
// src/core/vdom/create-element.js
export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
 // 如果传入的data是数组,那么在调用_createElement时,data开始每一项参数后移,data置空
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
	// 如果最后一个参数为true,那么调用_createElement的最后一个参数为2,否则为1
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
	// 最终调用_createElement()方法
  return _createElement(context, tag, data, children, normalizationType)
}

createElement方法实际上是对 _createElement() 方法的封装,处理了传入的参数。

const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // ...
	// 从传入到对应的判断过来的就是 
	// * normalizeChildren表示用户自己写的render函数
	// * simpleNormalizeChildren表示编译template模板生成的render函数
  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)
	  // 针对tag的类型做了判断
	  // 如果tag是浏览器原生标签类型的话,则直接生成对应的VNode
    if (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    }
	  // ...
  } else {
	  // 如果tag不为string,则执行创建组件操作
    // direct component options / constructor
	  // [[接组件化章节 1.createComponent 生成 VNode]]
    vnode = createComponent(tag, data, context, children)
  }
  // 最终将vnode返回
  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()
  }
}

_createElement() 主要做了:

  • 判断normalizationType,对children做对应的操作 normalizeChildrensimpleNormalizeChildren
  • 判断了tag类型,以及config.isReservedTag(tag)是否是相关原生浏览器保留节点等,决定如何生成相关vnode
  • 返回vnode 最终createElement根据render函数创建了VNode

normalizeChildren 与 simpleNormalizeChildren

// src/core/vdom/helpers/normalize-children.js
// 由template模板编译生成的render函数
// 对children进行了一次拍平操作,但仅仅操作了一次。
export function simpleNormalizeChildren (children: any) {
  for (let i = 0; i < children.length; i++) {
 	// 如果发现子元素是一个数组,则进行铺平操作
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}

// 用户手写的render函数
// 深度遍历递归,对children拍平操作。
export function normalizeChildren (children: any): ?Array<VNode> {
	/*
	首先判断children是否是一个一个基本数据类型
	isPrimitive(){
		return (
    		typeof value === 'string' ||
    		typeof value === 'number' ||
    		// $flow-disable-line
    		typeof value === 'symbol' ||
    		typeof value === 'boolean'
  	)
	}
	*/
  return isPrimitive(children)
    ? [createTextVNode(children)]  // 如果是就创建一个TextVNode
    : Array.isArray(children) // 如果不是则判断是否是数组
      ? normalizeArrayChildren(children)
      : undefined
}
// 是数组执行normalizeArrayChildren
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  const res = []
  let i, c, lastIndex, last
  for (i = 0; i < children.length; i++) {
    c = children[i]
    if (isUndef(c) || typeof c === 'boolean') continue
    lastIndex = res.length - 1
    last = res[lastIndex]
    //  nested
	  // 遍历了children,如果children[i]依然是数组,则进行递归处理
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
          c.shift()
        }
        res.push.apply(res, c)
      }
    } else if (isPrimitive(c)) {
		// ....
     	// 原生节点处理
      res.push(createTextVNode(c))
    } else {
      // ... 
		// 其他情况处理
      res.push(c)
    }
  }
  return res
}

normalizeChildren 与 simpleNormalizeChildren 都是对children做规范化的处理,使children变成一个类型为VNode的Array