createElement
Vue.js 利用 createElement 方法创建 VNode,它定义在 中:
方法实际上是对 方法的封装,它允许传入的参数更加灵活,在处理这些参数后,调用真正创建 VNode 的函数 :
方法有 5 个参数, 表示 VNode 的上下文环境,它是 类型; 表示标签,它可以是一个字符串,也可以是一个 ; 表示 VNode 的数据,它是一个 类型,可以在 中找到它的定义,这里先不展开说; 表示当前 VNode 的子节点,它是任意类型的,它接下来需要被规范为标准的 VNode 数组; 表示子节点规范的类型,类型不同规范的方法也就不一样,它主要是参考 函数是编译生成的还是用户手写的。
函数的流程略微有点多,我们接下来主要分析 2 个重点的流程 —— 的规范化以及 VNode 的创建。
children 的规范化
由于 Virtual DOM 实际上是一个树状结构,每一个 VNode 可能会有若干个子节点,这些子节点应该也是 VNode 的类型。 接收的第 4 个参数 children 是任意类型的,因此我们需要把它们规范成 VNode 类型。
这里根据 的不同,调用了 和 方法,它们的定义都在 中:
方法调用场景是 函数是编译生成的。理论上编译生成的 都已经是 VNode 类型的,但这里有一个例外,就是 函数式组件返回的是一个数组而不是一个根节点,所以会通过 方法把整个 数组打平,让它的深度只有一层。
方法的调用场景有 2 种,一个场景是 函数是用户手写的,当 只有一个节点的时候,Vue.js 从接口层面允许用户把 写成基础类型用来创建单个简单的文本节点,这种情况会调用 创建一个文本节点的 VNode;另一个场景是当编译 、 的时候会产生嵌套数组的情况,会调用 方法,接下来看一下它的实现:
接收 2 个参数, 表示要规范的子节点, 表示嵌套的索引,因为单个 可能是一个数组类型。 主要的逻辑就是遍历 ,获得单个节点 ,然后对 的类型判断,如果是一个数组类型,则递归调用 ; 如果是基础类型,则通过 方法转换成 VNode 类型;否则就已经是 VNode 类型了,如果 是一个列表并且列表还存在嵌套的情况,则根据 去更新它的 key。这里需要注意一点,在遍历的过程中,对这 3 种情况都做了如下处理:如果存在两个连续的 节点,会把它们合并成一个 节点。
经过对 的规范化, 变成了一个类型为 VNode 的 Array。
VNode 的创建
回到 函数,规范化 后,接下来会去创建一个 VNode 的实例:
这里先对 做判断,如果是 类型,则接着判断如果是内置的一些节点,则直接创建一个普通 VNode,如果是为已注册的组件名,则通过 创建一个组件类型的 VNode,否则创建一个未知的标签的 VNode。 如果是 一个 类型,则直接调用 创建一个组件类型的 VNode 节点。对于 创建组件类型的 VNode 的过程,我们之后会去介绍,本质上它还是返回了一个 VNode。
总结
那么至此,我们大致了解了 创建 VNode 的过程,每个 VNode 有 , 每个元素也是一个 VNode,这样就形成了一个 VNode Tree,它很好的描述了我们的 DOM Tree。
回到 函数的过程,我们已经知道 是如何创建了一个 VNode,接下来就是要把这个 VNode 渲染成一个真实的 DOM 并渲染出来,这个过程是通过 完成的,接下来分析一下这个过程。
领取专属 10元无门槛券
私享最新 技术干货