持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
本篇文章会对之前的代码进行重构,主要是重构区分组件类型的逻辑,组件的类型目前已实现的有Component组件类型、Element原生DOM类型,并且子组件也分为TEXT_CHILDREN,即纯文本的子组件,和ARRAY_CHILDREN,数组类型的子组件
由于组件类型的判断将会经常用到,那么实现一个有效且高性能的方式去区分组件类型很有必要,这就是本节要讲的ShapeFlags的作用,可以使用对象的方式去实现,但是为了性能更高,会采用位运算的方式实现
1. 实现 ShapeFlags 进行重构,提升性能
目前我们区分vnode.type是直接通过判断语句中使用typeof和isObject去区分vnode的类型的,这种方式有点偏向底层了,可读性不够高,如果能够实现一种机制,给每一个vnode加上一个标识属性,标识它是什么类型的话就好了
这就是ShapeFlags的意义,可以给vnode添加一个shapeFlag属性,根据该属性去判断,提高代码可读性
1.1 使用对象方式实现
可以维护一个ShapeFlags对象,类似下面这样
const ShapeFlags = {
element: 0,
stateful_component: 0,
text_children: 0,
array_children: 0,
};
初始值是全都为0,当确定了一个vnode的类型的时候,就将对应的flag置为1,比如有一个vnode是Element类型的,那么就可以像这样子判断:
// 设置 shapeFlag
vnode.shapeFlag.element = 1;
// 判断
if (vnode.shapeFlag.element) {
// 处理 Element 类型的逻辑
}
但其实这种对象的方式不够高效,为了让性能更加高,我们会采用位运算的方式实现
1.2 使用位运算方式实现
可以定义一个枚举,里面存放vnode的类型,但是是通过位运算的方式实现,比如下面这样:
// src/shared/shapeFlags.ts
export const enum ShapeFlags {
ELEMENT = 1,
STATEFUL_COMPONENT = 1 << 1,
TEXT_CHILDREN = 1 << 2,
ARRAY_CHILDREN = 1 << 3,
}
然后就可以在创建vnode的时候根据type去初始化它的shapeFlag
vnode.shapeFlag = ShapeFlags.ELEMENT;
要判断的时候可以通过与运算**&**去处理
if (vnode.shpaeFlag & ShapeFlags.ELEMENT) {
// do something...
}
// ELEMENT 0001 & 0001 === 0001 判断通过
// STATEFUL_COMPONENT 0010 & 0001 === 0000 判断不通过
而如果是要添加新的类型,比如已知子vnode是ELEMENT类型,并且已知是它里面没有新的子节点了,只有纯文本,也就是TEXT_CHILDREN类型,那么就可以通过或运算**|**的方式去添加
vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
// 0001 | 0100 === 0101 --> 表明既是 ELEMENT 又是 TEXT_CHILDREN 类型
if (vnode.shapeFlag & ShapeFlags.ELEMENT) {
}
// 0101 & 0001 === 0001 --> ELEMENT
if (vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN) {
}
// 0101 & 0100 === 0100 --> TEXT_CHILDREN
那么现在有了ShapeFlags枚举,我们就可以用它去替换vnode类型判断的代码以及新增初始化vnode.shapeFlag的代码了
1.2.1 初始化 vnode.shapeFlag
在创建vnode的时候给vnode添加shapeFlag属性定义
export function createVNode(type, props?, children?) {
const vnode = {
type,
props,
children,
shapeFlag: getShapeFlag(type),
el: null,
};
return vnode;
}
function getShapeFlag(type) {
return typeof type === 'string'
? ShapeFlags.ELEMENT
: ShapeFlags.STATEFUL_COMPONENT;
}
以及需要根据children是文本还是数组去添加shapeFlag的类型
export function createVNode(type, props?, children?) {
const vnode = {
type,
props,
children,
shapeFlag: getShapeFlag(type),
el: null,
};
+ // 根据 children 的类型添加 vnode 的类型 -- 是 TEXT_CHILDREN 还是 ARRAY_CHILDREN
+ if (typeof children === 'string') {
+ vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
+ } else if (Array.isArray(children)) {
+ vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
+ }
return vnode;
}
1.2.2 重构 vnode 类型判断代码
找出原来的判断vnode类型的代码,用vnode.shapeFlag属性去重构
首先是renderer.ts的patch函数中有用到,会根据vnode是Component类型还是Element类型去走不同的分支处理
export function patch(vnode, container) {
- const { type } = vnode;
+ const { type, shapeFlag } = vnode;
- if (typeof type === 'string') {
+ if (shapeFlag & ShapeFlags.ELEMENT) {
// 真实 DOM
processElement(vnode, container);
- } else if (isObject(type)) {
+ } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 处理 component 类型
processComponent(vnode, container);
}
}
然后是mountElement中对children的判断
function mountElement(vnode: any, container: any) {
// 将 DOM 对象挂载到 vnode 上 从而让组件实例能够访问到
const el = (vnode.el = document.createElement(vnode.type));
- const { children } = vnode;
+ const { children, shapeFlag } = vnode;
- if (typeof children === 'string') {
+ if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
el.textContent = children;
- } else if (Array.isArray(children)) {
+ } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
}
// props
const { props } = vnode;
for (const [key, value] of Object.entries(props)) {
el.setAttribute(key, value);
}
container.append(el);
}
重构完后再build看看,如果不影响之前的功能说明重构没问题啦!