手写 mini-vue3 实现 ShapeFlags(四)

28 阅读2分钟

为什么使用 | & 运算符

在Vue.js源码中,ShapeFlags 是一个标志位枚举,用于表示不同类型的 VNode(虚拟节点)。这些标志位用于在创建和处理 VNode 时快速检查节点的类型和特性。

使用 (或运算)和 &(与运算)的目的在于将多个标志位组合到一个整数中。这样做有以下好处:

  1. 紧凑表示: 将多个标志位通过位运算合并到一个整数中,可以用更紧凑的方式表示一个 VNode 的特性。这有助于减小内存占用。
  2. 快速检查: 通过使用 & 运算,你可以快速检查一个 VNode 是否具有某个特定的标志位,而不需要逐个检查每个标志位。

下面是一个简单的示例,说明如何使用 & 运算符:

const ShapeFlags = {
  ELEMENT: 1,
  FUNCTIONAL_COMPONENT: 1 << 1,
  STATEFUL_COMPONENT: 1 << 2,
  TEXT_CHILDREN: 1 << 3,
  ARRAY_CHILDREN: 1 << 4,
  SLOTS_CHILDREN: 1 << 5
};

// 创建一个具有 ELEMENT 和 TEXT_CHILDREN 标志位的 VNode
const vnode = ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN;

// 检查 VNode 是否具有 TEXT_CHILDREN 标志位
if (vnode & ShapeFlags.TEXT_CHILDREN) {
  console.log('具有 TEXT_CHILDREN 标志位');
}

在这个示例中,每个标志位都是通过位移运算得到的。例如,1 << 1 表示将二进制中的 1 左移一位,得到二进制 10,即 2。这样,每个标志位都是不同的 2 的幂,方便在二进制表示中进行组合和检查。

实现 ShapeFlags

// runtime-core/vnode.ts
export function createVNode(type, props?, children?) {
  const vnode = {
    type,
    props,
    component: null,
    children,
    shapeFlag: getShapeFlags(type), // 初步根据 type 判断 shapeFlag
    el: null,
    key: props?.key,
  };

  // 针对 children 进一步判断 shapeFlag
  if (typeof children === "string") {
    // 使用 | 运算符,这样就能兼顾 type 和 children
    vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
  } else if (Array.isArray(children)) {
    vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
  }

  // slots 的条件:组件 + children 是 object
  if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
    if (typeof children === "object") {
      vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
    }
  }

  return vnode;
}

function getShapeFlags(type) {
  return typeof type === "string"
    ? ShapeFlags.ELEMENT
    : ShapeFlags.STATEFUL_COMPONENT;
}
// runtime-core/renderer.ts
function patch(vnode, container) {
    const { shapeFlag, type } = vnode;
    if (shapeFlag & ShapeFlags.ELEMENT) {
      // 处理 Element
      processElement(vnode, container);
    } else {
      // 处理组件
      processComponent(vnode, container);
    }
}
// shared.ts
export enum ShapeFlags {
  ELEMENT = 1,
  STATEFUL_COMPONENT = 1 << 1,
  TEXT_CHILDREN = 1 << 2,
  ARRAY_CHILDREN = 1 << 3,
  SLOT_CHILDREN = 1 << 4,
}

// | 运算符逻辑,同时为 0 才是 0,(只要有 1 就是 1)
// & 运算符逻辑,同时为 1 才是 1,(只要有 0 就是 0)