h 方法实现

第一步

h 函数主要是用来创建虚拟 DOM的,他的参数情况比较多,参数情况可以有 1个(类型),2个(类型,属性/儿子),3个(类型, 属性, 儿子),超过 3 个(从第三个开始后面都是儿子),如下:

1
2
3
h(类型)
h(类型, 属性, 儿子)
h(类型, 儿子)

两个参数的情况:

1. 两个参数的时候,第二个参数可能是属性,或者虚拟节点(根据 `__v_isVnode` 来判断)
1. 第二个参数的数组的话,那第二个参数肯定是儿子
1. 直接传递非对象的话,那说明就是文本
1. 其他情况下,就都是属性

三个参数的时候,第二个参数必须是属性

packages/runtime-core/src/h.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { isObject } from '@vue/shared';
import { createVnode, isVnode } from './createVnode';

export function h(type, propsOrChildren?, children?) {
const l = arguments.length;
if (l === 2) {
if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
// 虚拟节点
if (isVnode(propsOrChildren)) {
return createVnode(type, null, [ propsOrChildren ]);
} else {
// 属性
return createVnode(type, propsOrChildren);
}
}

// 儿子是数组或者文本
return createVnode(type, null, propsOrChildren);
} else {
if (l > 3) {
children = Array.from(arguments).slice(2);
}
if (l === 3 && isVnode(children)) {
children = [ children ];
}

return createVnode(type, propsOrChildren, children);
}
}

packages/runtime-core/src/createVnode.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { isString, ShapeFlags } from '@vue/shared';

export function createVnode(type, props, children?) {
const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;
const vnode = {
__v_isVnode: true,
type,
props,
children,
key: props?.key,
el: null,
shapeFlag
};

if (children) {
if (Array.isArray(children)) {
vnode.shapeFlag = vnode.shapeFlag | ShapeFlags.ARRAY_CHILDREN;
} else {
children = String(children);
vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
}
}
return vnode;
}

export function isVnode(value) {
return value?.__v_isVnode;
}

export function isSameVnode(n1, n2) {
return n1.type === n2.type && n1.key === n2.key;
}

export const Text = Symbol('text');
export const Fragment = Symbol('Fragment');

packages/shared/src/index.ts

1
2
3
4
5
...
export function isString(value) {
return typeof value === 'string';
}
...