在看虚拟节点一节中,可以看到虚拟节点是涉及到4种类型的;
背景:由虚拟节点开始🤔
一种 我们直接调用h函数创建虚拟节点
function h(type, props?, children?) {
return createVNode(type, props, children)
}
function createVNode(type, props, children) {
var vnode = {
type: type,
props: props,
children: children,
};
return vnode;
}
// 调用h创建虚拟节点
h(
'div', // type
{id: 'root', class: ['red', 'blue']}, // porps
[
h("p", { class: 'red'}, "hi"),
h("p", { class: 'blue'}, "mini-vue")
] // children
// chilren也可能是简单的文本 比方<div id="root">这里是chilrend就是普通文本</div>
)
第二种 创建app时传入根组件也会被直接转换成虚拟节点-》component -> vnode
// 其次 createApp时传入【根组件】时也会直接转换成虚拟节点
function createApp (rootComponent) {
return {
// rootContainer根容器
mount(rootContainer) {
// 把所有的东西转换成vnode 后续素有的逻辑操作都会基于 vnode 做处理
// component -> vnode
const vnode = createVNode(rootComponent)
render(vnode, rootContainer)
}
}
}
export const App = {
// 平时我们写的是.vue类的文件 <template></template> 这是需要有编译功能的这里暂时不考虑这个
// 我们用render函数‘ template最终其实也会被编译成render函数被其处理
render() {
return h('div',{
id: 'root',
class: ['red', 'blue']
},
"hello world"
)
},
setup() {
return {
msg: 'mini-vue 哈哈哈'
}
},
}
那么此时createVNode中的type类型就会有两种 一种是普通的div即普通element其typeof为string;
第二种传入的是一个App带有状态的component其typeof为object;
接着考虑其children 有可能是普通的一段文本string; 也可能是数组array需要继续patch递归
因此我们就写出了如下的代码
function patch(vnode, container) {
// 去处理组件
// 判断虚拟节点类型 preocessComponent / processElement
// 判断是不是element
if (typeof vnode.type === "string") {
processElement(vnode, container);
} else if (isObject(vnode.type)) {
processComponent(vnode, container)
}
}
function mountElement(vnode: any, container: any) {
// 看vnode.ts中 vnode = { type, props, chidren }
// const el = document.createElement('div') // type
// el.setAttribute('id', 'root') // props
// // chidren 涉及两种类型 一种string类型; 一种array类型
// el.textContent = 'hi mini-vue'
// document.body.appendChild(el);
const { type, props, children } = vnode
// 尝试将el存储起来 有多个h会有多个el; 但是只有type为div,id为root的才是render的得到的第一层里的el会被最终$el读取
const el = (vnode.el = document.createElement(type)) // type
for (const key in props) {
const val = props[key]
el.setAttribute(key, val)
}
if (typeof children === 'string') {
el.textContent = children
} else if (Array.isArray(children)) {
mountChildren(vnode, el)
}
container.appendChild(el)
}
由于我们已经知道虚拟节点创建到渲染,type和children会涉及4中元素,这时为了逻辑统一,我们一般会这样写
一、有限属性--对象属性的修改和读取
const shapeFlags = {
ELEMENT: 0,
STATEFUl_COMPONENT: 0,
TEXT_CHILDREN: 0,
ARRAY_CHILDREN: 0
}
// 将shapeFlags添加到vnode上
function createVNode(type, props, children) {
var vnode = {
type: type,
props: props,
children: children,
shapeFlags: shapeFlags, // 这里是一个对象
};
function getShapsFlags(type) {
typeof type === 'string' ? ShapeFlags.ELEMENT = 1 : ShapeFlags.STATEFUl_COMPONENT = 1
return ShapeFlags
}
typeof children === 'string' ? shapeFlags.TEXT_CHILDREN = 1 : shapeFlags.ARRAY_CHILDREN = 1
return vnode;
}
1. 设值 修改
// 如果此时进来的虚拟节点的type是stateful_component
shapeFlags.STATEFUl_COMPONENT = 1
// 如果此时进来的虚拟节点的type是stateful_component; 且children是array_children
shapeFlags.STATEFUl_COMPONENT = 1
shapeFlags.ARRAY_CHILDREN = 1
2. 读取
function patch(vnode, container) {
const { shapeFlags } = vnode
if (shapeFlags.ELEMENT) {
processElement(vnode, container);
} else if (shapeFlags.STATEFUl_COMPONENT) {
processComponent(vnode, container)
}
}
function mountElement(vnode: any, container: any) {
// ……
const { type, props, children, shapeFlags } = vnode
// 其他……
if (shapeFlags.TEXT_CHILDREN) {
el.textContent = children
} else if (shapeFlags.ARRAY_CHILDREN) {
mountChildren(vnode, el)
}
container.appendChild(el)
}
二、 更高效的方式--位运算
// 0000 -> 默认
// 0001 -> element类型
// 0010 -> stateful_component类型
// 0100 -> text_children类型
// 1000 -> array_children类型
// 此时 如果节点是element类型,chilren是array_children类型;那就是
// -> 1001
1. 位运算的学习
1.1 按位与:&
um1 & num2:转为二进制,按位相与 同1则1, 符号位参与运算
&&运算符我们都知道,只有两个都为真,结果才为真。
&道理是一样的,只有两个数的值为1时,才返回1。例如1和3的按位与操作:
0001
& 0011
---------
0001
1.2 ## 按位或 |
~num:转换为二进制按位取反 0变1,1变0, 符号位参与运算
与||操作符的道理也是一样的,只要两个数中有一个数为1,结果就为1,其他则为0。
0001
| 0011
---------
0011
2. 改造上述
所以我们要设置值时
2.1 设值 修改
用|同为0才是0
0001 -> 1001
那就是 0001
|1000
------
1001
``
### 2.2 查找
用&同为1才是1
```ts
1001 -〉 查找是否有0001
1001
& 0001
------
0001
三、位运算重构上述
1. 设值 修改
// `num<<n`:将 `num` 转为二进制,左移 `n` 位(当然,输出的结果是十进制的)
const enum ShapeFlags {
ELEMENT = 1, //
STATEFUl_COMPONENT = 1 << 1, // 0010
TEXT_CHILDREN = 1 << 2, // 0100
ARRAY_CHILDREN = 1 << 3, // 1000
}
// 将shapeFlags添加到vnode上
function createVNode(type, props, children) {
var vnode = {
type: type,
props: props,
children: children,
shapeFlag: getShapsFlag(type), // 这里就是一个普通的二进制数值
};
function getShapsFlag(type) {
return typeof type === 'string' ? ShapeFlags.ELEMENT : ShapeFlags.STATEFUl_COMPONENT
}
if (typeof children === 'string') {
// 比方vnode.shapeFlags经过getShapsFlag变成了0001;0001 | 0100 就是0101
vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN
// 即 vnode.shapeFlags = vnode.shapeFlags | ShapeFlags.TEXT_CHILDREN 的简写
} else {
vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN
}
return vnode;
}
2. 读取
function patch(vnode, container) {
const { shapeFlag } = vnode // 比方0101 查找是否有0001 用&
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(vnode, container);
} else if (shapeFlag & ShapeFlags.STATEFUl_COMPONENT) {
processComponent(vnode, container)
}
}
function mountElement(vnode: any, container: any) {
// ……
const { type, props, children, shapeFlag } = vnode
// 其他……
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
el.textContent = children
} else if (shapeFlag & shapeFlags.ARRAY_CHILDREN) {
mountChildren(vnode, el)
}
container.appendChild(el)
}
总结,位运算设置和查找更为高效,但同时损失了可读性; 性能和可读性有时会是相冲的;这时要自己根据情况进行取舍