Vue源码解析系列——数据驱动篇:createElement的执行过程

准备

vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。

回顾

如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》

createElement的定义

createElement定义在core/vdom/create-element.js中。
这边其实做了一个参数的重载的一个兼容性写法。如果data是个数组或者为基本类型(其实这两种类型是恰恰对应的children),就把data之后的参数都往前赋值一个位置,然后将data置空。

export function createElement(
 context: Component,
 tag: any,
 data: any,
 children: any,
 normalizationType: any,
 alwaysNormalize: boolean
): VNode | Array<VNode> {
 /**
  * 参数重载的实现
  * 当data为数组时,也就是说data传入的应该是childeren
  * data后面的参数全部往前移一位,赋值data为undefined
  */
 if (Array.isArray(data) || isPrimitive(data)) {
   normalizationType = children;
   children = data;
   data = undefined;
 }
 if (isTrue(alwaysNormalize)) {
   normalizationType = ALWAYS_NORMALIZE;
 }
 return _createElement(context, tag, data, children, normalizationType);
}

最后调用的其实是_createElement,并讲结果返回。可以看到的是createElemet这个函数其实是对参数做了一层封装,真正调用的还是_createElement这个私有方法。

_createElement

接下来我们来看下_createElement

export function _createElement(
 context: Component,
 tag?: string | Class<Component> | Function | Object,
 data?: VNodeData,
 children?: any,
 normalizationType?: number
): VNode | Array<VNode> {
 //对data进行一层检查,需要的data不能是响应式的
 if (isDef(data) && isDef((data: any).__ob__)) {
   process.env.NODE_ENV !== "production" &&
     warn(
       `Avoid using observed data object as vnode data: ${JSON.stringify(
         data
       )}\n` + "Always create fresh vnode data objects in each render!",
       context
     );
   return createEmptyVNode();
 }

先是对data做了一层检查,因为这里需要的data不能是响应式的data
继续往下看:

if (normalizationType === ALWAYS_NORMALIZE) {
  children = normalizeChildren(children);
} else if (normalizationType === SIMPLE_NORMALIZE) {
  children = simpleNormalizeChildren(children);
}

这里先判断normalizationType,判断这个的作用是:用不同的方法对children做规范化,毕竟redner函数既可以是模板编译器编译出来的,也可以是用户手写的。

  • 如果是模板编译器编译的,那children就是可控的数据结构,更加符合Vue的预期,所以只需要做一点点简单的规范化。
    是对children做了一个规范化的处理
  • 如果是用户手写的render那这个children的数据结构就是不可控的,需要强规范化。

这样,我们就大致了解了为什么这里需要判断并使用不同的规范方法。

simpleNormalizeChildren

我们先进入simpleNormalizeChildren看看简单的规范是如何处理的:

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
}

这里使用了数组的concat方法将一个二维数组拍成一维数组,但是更高维的不能拍平。

normalizeArrayChildren

然后再看看normalizeArrayChildren:

export function normalizeChildren(children: any): ?Array<VNode> {
 return isPrimitive(children)
   ? [createTextVNode(children)]
   : Array.isArray(children)
   ? normalizeArrayChildren(children)
   : undefined;
}

这边分别对children的各种类型使用了不同的处理方法,这里我们主要看的是数组类型,也就是normalizeArrayChildren这个方法:

/**
* 深度递归children,拍平成一个一维数组
* @param {*} children
* @param {*} nestedIndex
*/
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];
   //如果子元素还是一个数组,就递归
   if (Array.isArray(c)) {
     if (c.length > 0) {
       c = normalizeArrayChildren(c, `${nestedIndex || ""}_${i}`);
       //这里是一个小优化:如果前面一个数组元素是一个文本节点,且本次的元素
       //也是一个文本节点,就合并两个文本节点
       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)) {
     if (isTextNode(last)) {
       //如果前一个元素是文本节点且本次的元素是一个基本类型的值,也是合并两个节点
       res[lastIndex] = createTextVNode(last.text + c);
     } else if (c !== "") {
       res.push(createTextVNode(c));
     }
   } else {
     if (isTextNode(c) && isTextNode(last)) {
       res[lastIndex] = createTextVNode(last.text + c.text);
     } else {
       if (
         isTrue(children._isVList) &&
         isDef(c.tag) &&
         isUndef(c.key) &&
         isDef(nestedIndex)
       ) {
         c.key = `__vlist${nestedIndex}_${i}__`;
       }
       res.push(c);
     }
   }
 }
 return res;
}

深度优先遍历数组。
每次缓存上一个数组元素的值,还做了一个小优化:合并文本节点。
好的,我们回到'_createElement

let vnode, ns;
 /**
  * 如果tag是字符串,
  * 如果tag不是字符串(是Vue组件对象),就创建一个组件赋值给vnode
  */
 if (typeof tag === "string") {
   let Ctor;
   ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
   //如果tag是html原生标签
   if (config.isReservedTag(tag)) {
     // platform built-in elements
     if (
       process.env.NODE_ENV !== "production" &&
       isDef(data) &&
       isDef(data.nativeOn)
     ) {
       warn(
         `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
         context
       );
     }
     vnode = new VNode(
       config.parsePlatformTagName(tag),
       data,
       children,
       undefined,
       undefined,
       context
     );
   } else if (
     (!data || !data.pre) &&
     isDef((Ctor = resolveAsset(context.$options, "components", tag)))
   ) {
     //这边不知道
     // component
     vnode = createComponent(Ctor, data, context, children, tag);
   } else {
     //如果tag是未知命名空间的元素,就新建一个vnode
     // unknown or unlisted namespaced elements
     // check at runtime because it may get assigned a namespace when its
     // parent normalizes children
     vnode = new VNode(tag, data, children, undefined, undefined, context);
   }
 } else {
   //如果tag是组件对象,就创建组件
   // direct component options / constructor
   vnode = createComponent(tag, data, context, children);
 }

判断tag是不是组件,如果是组件就执行createComponent(这个我们之后再讲),如果是平台保留标签(这里我以web平台为主),就创建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();
 }

对VNode做了一些判断降级处理,最终返回。
至此,createElement阅读完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的前端小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值