归一化操作其实就是将多维的数组,合并转换成一个一维的数组。在我们Vue
源码中,它分为三个级别,0
表示不需要进行归一化处理,1
表示只需要简单的归一化处理,2
表示要考虑到所有的情况,此时归一化处理会比较复杂。
模板编译器尝试在编译时静态分析模板来使归一化的需求降到最低(就是尽量不用归一化)。
简单的html
标签由于生成的render
函数返回的是一个VNode
数组,可以直接跳过归一化。有两种情况需要额外的归一化处理:
-
子元素包含自定义
component
——因为函数式的component
可能返回一个数组而不是一个根节点,这种情况下,我们只需要简单的归一化,如果子元素是数组,则通过Array.prototype.concat
来把子元素合并为一个数组。最终生成的是一个一维数组,因为component
已经对他自己的子内容进行了相同的操作。 -
子元素包含生成嵌套数组的结构,比如
<template>
、<slot>
、v-for
或子元素是用户手写的render
函数。这个时候我们就需要完全归一化来处理所有可能的children
值。
我们用户手写的render
函数,都会进行完全的归一化处理,而通过HTML
模板解析生成的render
函数,会根据内容的不同而进行不同级别的归一化处理。
该部分代码在src/core/vdom/helpers/normalize-children.js
文件中。
简单归一化处理:
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
}
该过程很简单,如果children
中有元素是数组,则直接通过Array.prototype.concat.apply([], children)
来把整个children
合并为一个数组,而不用去考虑里面的具体情况。
完全归一化处理:
export function normalizeChildren (children: any): ?Array<VNode> {
return isPrimitive(children)
? [createTextVNode(children)]
: Array.isArray(children)
? normalizeArrayChildren(children)
: undefined
}
如果我们传入的children
是字符串或者数字,则直接返回文本结点数组,比如如下例子:
render: function(h){
// return h('div', "test");
return h('div', 213);
}
如果我们传入的children
是一个数组,则通过normalizeArrayChildren
进行处理。
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
const res = []
let i, c, last
for (i = 0; i < children.length; i++) {
c = children[i]
if (c == null || typeof c === 'boolean') continue
last = res[res.length - 1]
// nested
if (Array.isArray(c)) {
res.push.apply(res, normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`))
} else if (isPrimitive(c)) {
if (last && last.text) {
last.text += String(c)
} else if (c !== '') {
// convert primitive to vnode
res.push(createTextVNode(c))
}
} else {
if (c.text && last && last.text) {
res[res.length - 1] = createTextVNode(last.text + c.text)
} else {
// default key for nested array children (likely generated by v-for)
if (c.tag && c.key == null && nestedIndex != null) {
c.key = `__vlist${nestedIndex}_${i}__`
}
res.push(c)
}
}
}
return res
}
我们来简单看一下它都做了哪些处理。
首先创建了一个新的数组res
,然后循环children
数组:
1、如果c
是undefined
或null
或Boolean
类型的值,则直接忽略。
2、如果c是一个数组,则递归的执行
normalizeArrayChildren`方法,使多维数组转换为一维的。例:
var h = vm.$createElement;
h('div', ["test", [h('p'), h('p')], null, true])
最终转换之后的children
包含三个元素VNode
对象,第一个是文本为“text”的文本结点,然后依次是两个p
标签。而最后两个被忽略。
3、如果c
是字符串或者数字,则判断res
中最后一个元素是不是文本结点,如果是则合并结点,如果不是且c
不是空字符串,则创建一个新的文本结点。
4、如果c
是一个文本VNode
对象,且res
中最后一个元素也是文本结点,则合并两个VNode
为一个。否则如果c
是Vnode
对象,若c.tag
存在,且c.key
不存在,且nestedIndex
存在(第二步中递归调用时会传入),则设置c.key
的值,也就是说会给嵌套的VNode
对象添加key
。
最终返回res
数组。