vue3源码分析 -- h

上篇runtime中我们了解到,虚拟 DOM是 Vue 在运行时,通过h函数获取到VNode对象,这里我们就来看下h函数是如何实现的

案例

首先引入h函数,之后通过h函数生成一个vnode对象,并将其打印

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../../dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { h } = Vue

      debugger
      const vnode = h(
        'div',
        {
          class: 'test'
        },
        'hello render'
      )
      console.log(vnode)
    </script>
  </body>
</html>

h函数定义在packages/runtime-core/src/h.ts文件下:

可以看出h函数接收三个参数,当前typedivpropsOrChildren{ class: 'test'}childrenhello render

在这里插入图片描述

createVNode方法

之后根据参数的长度不同走不同的判断逻辑,其核心是执行createVNode方法,实际执行的是_createVNode,该方法在packages/runtime-core/src/vnode.ts文件中:

export const createVNode = (
  __DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    if (__DEV__ && !type) {
      warn(`Invalid vnode type when creating vnode: ${type}.`)
    }
    type = Comment
  }
  // 是否是 vnode 通过 __v_isVNode 来判断
  if (isVNode(type)) {
    // createVNode receiving an existing vnode. This happens in cases like
    // <component :is="vnode"/>
    // #2078 make sure to merge refs during the clone instead of overwriting it
    const cloned = cloneVNode(type, props, true /* mergeRef: true */)
    if (children) {
      normalizeChildren(cloned, children)
    }
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
        currentBlock[currentBlock.indexOf(type)] = cloned
      } else {
        currentBlock.push(cloned)
      }
    }
    cloned.patchFlag |= PatchFlags.BAIL
    return cloned
  }

  // class component normalization.
  if (isClassComponent(type)) {
    type = type.__vccOpts
  }

  // 2.x async/functional component compat
  if (__COMPAT__) {
    type = convertLegacyComponent(type, currentRenderingInstance)
  }

  // class & style normalization.
  // class 和 style 的增强
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)! // 解析 props
    let { class: klass, style } = props // 结构  class 赋值给 klass, style
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass) // 增强 class
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }

  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type) // 根据 type 类型进行 shapeFlag 赋值 当前为 div 则 ShapeFlags.ELEMENT
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0

  if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
    type = toRaw(type)
    warn(
      `Vue received a Component which was made a reactive object. This can ` +
        `lead to unnecessary performance overhead, and should be avoided by ` +
        `marking the component with `markRaw` or using `shallowRef` ` +
        `instead of `ref`.`,
      `\nComponent that was made reactive: `,
      type
    )
  }

  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

这里isVNode(type)通过判断type是否是 VNode,我们来看下isVNode方法:

export function isVNode(value: any): value is VNode {
  return value ? value.__v_isVNode === true : false
}

主要通过__v_isVNode属性来判断是否是VNode,之后再判断props即传入的{ class: 'test'},对classstyle增强,这块我们放到之后来讨论

接着又对shapeFlag赋值,当前type为 string 类型,此时被赋值为ShapeFlags.ELEMENT即等于1,最后将处理好的typepropschildrenshapeFlag等参数传入createBaseVNode方法中

在这里插入图片描述

_createVNode方法核心:

  • classstyle增强
  • shapeFlag标记赋值

shapeFlag标记赋值

接着再看下createBaseVNode方法:

function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
  const vnode = {
    __v_isVNode: true,
    __v_skip: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    slotScopeIds: null,
    children,
    component: null,
    suspense: null,
    ssContent: null,
    ssFallback: null,
    dirs: null,
    transition: null,
    el: null,
    anchor: null,
    target: null,
    targetAnchor: null,
    staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null,
    appContext: null
  } as VNode

  if (needFullChildrenNormalization) {
    normalizeChildren(vnode, children) // 创建子节点
    // normalize suspense children
    if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
      ;(type as typeof SuspenseImpl).normalize(vnode)
    }
  } else if (children) {
    // compiled element vnode - if children is passed, only possible types are
    // string or Array.
    vnode.shapeFlag |= isString(children)
      ? ShapeFlags.TEXT_CHILDREN
      : ShapeFlags.ARRAY_CHILDREN
  }

  // validate key
  if (__DEV__ && vnode.key !== vnode.key) {
    warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
  }

  // track vnode for block tree
  if (
    isBlockTreeEnabled > 0 &&
    // avoid a block node from tracking itself
    !isBlockNode &&
    // has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.
    vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
  ) {
    currentBlock.push(vnode)
  }

  if (__COMPAT__) {
    convertLegacyVModelProps(vnode)
    defineLegacyVNodeProperties(vnode)
  }

  return vnode
}

该方法首先定义了一个vnode对象,属性__v_isVNode标记为该对象是否为VNode对象。由于当前needFullChildrenNormalization默认传入的是true,所以直接执行normalizeChildren(vnode, children)方法来创建子节点,再来看下normalizeChildren方法:

export function normalizeChildren(vnode: VNode, children: unknown) {
  let type = 0
  const { shapeFlag } = vnode // 当前shapeFlag 是 1 children是字符串
  // children 为 undefined 或 null
  if (children == null) {
    children = null
  } else if (isArray(children)) { // 是否是数组
    type = ShapeFlags.ARRAY_CHILDREN
  } else if (typeof children === 'object') { // 是否是对象
    if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.TELEPORT)) {
      // Normalize slot to plain children for plain element and Teleport
      const slot = (children as any).default
      if (slot) {
        // _c marker is added by withCtx() indicating this is a compiled slot
        slot._c && (slot._d = false)
        normalizeChildren(vnode, slot())
        slot._c && (slot._d = true)
      }
      return
    } else {
      type = ShapeFlags.SLOTS_CHILDREN
      const slotFlag = (children as RawSlots)._
      if (!slotFlag && !(InternalObjectKey in children!)) {
        // if slots are not normalized, attach context instance
        // (compiled / normalized slots already have context)
        ;(children as RawSlots)._ctx = currentRenderingInstance
      } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
        // a child component receives forwarded slots from the parent.
        // its slot type is determined by its parent's slot type.
        if (
          (currentRenderingInstance.slots as RawSlots)._ === SlotFlags.STABLE
        ) {
          ;(children as RawSlots)._ = SlotFlags.STABLE
        } else {
          ;(children as RawSlots)._ = SlotFlags.DYNAMIC
          vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
        }
      }
    }
  } else if (isFunction(children)) { // 是否是 函数
    children = { default: children, _ctx: currentRenderingInstance }
    type = ShapeFlags.SLOTS_CHILDREN
  } else {
    children = String(children) // 此时 'hello render'
    // force teleport children to array so it can be moved around
    if (shapeFlag & ShapeFlags.TELEPORT) {
      type = ShapeFlags.ARRAY_CHILDREN
      children = [createTextVNode(children as string)]
    } else {
      type = ShapeFlags.TEXT_CHILDREN
    }
  }
  vnode.children = children as VNodeNormalizedChildren
  // 9 按位或赋值  vnode.shapeFlag |= type 等同于 vnode.shapeFlag = vnode.shapeFlag | type
  vnode.shapeFlag |= type 
}

该方法接收两个参数,一个是定义的vnode对象,一个是childrenhello render。之后再从vnode对象中解构出shapeFlag,即当前 string 类型为 1,根据children类型不同对childrentypeshapeFlag重新赋值

由于当前children为 string 类型,执行children = String(children),此时childrenhello render,并将其vnode.children = children重新赋值。type = ShapeFlags.TEXT_CHILDREN type 为 8

在这里插入图片描述

最后对 vnode.shapeFlag |= type 按或位赋值即为 9

在这里插入图片描述

这里拓展下 |= 按或位赋值vnode.shapeFlag |= type 等同于 vnode.shapeFlag = vnode.shapeFlag | type。当前 vnode.shapeFlag = 8type = 1,转为二进制:

// type = 1
00000000 00000000 00000000 00000001

// shapeFlag = 8
00000000 00000000 00000000 00001000

// 或 就是通过上下 ↕ 比较,如果上下是 0 则是 0,上下是 0 和 1 则是 1
// 结果是 9 
00000000 00000000 00000000 00001001

所以此时计算后的 vnode.shapeFlag = 9,之后 createBaseVNode 执行完毕返回 vnode 对象,至此 h 函数执行完毕,打印 vnode 对象:

在这里插入图片描述

class、style增强

我们再回过来看下 h 函数如何对 class style 增强的,该逻辑在 _createVNode 方法中:

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  // 省略

  // class & style normalization.
  // class 和 style 的增强
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)! // 解析 props
    let { class: klass, style } = props // 结构  class 赋值给 klass, style
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass) // 增强 class
    }
    if (isObject(style)) {
      // reactive state objects need to be cloned since they are likely to be
      // mutated
      if (isProxy(style) && !isArray(style)) {
        style = extend({}, style)
      }
      props.style = normalizeStyle(style)
    }
  }
  // 省略
  
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

结合一下案例进行分析:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="../../dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { h, render } = Vue
      // <div :class="{ red: true }">增强的 class</div>
      debugger
      const vnode = h(
        'div',
        {
          class: {
            red: true
          }
        },
        '增强的 class'
      )
      render(vnode, document.querySelector('#app'))
    </script>
  </body>
</html>

可以看出 { class: klass, style } = props props 解构,并将 class 赋值给 klass,如果存在 klass 且不为 string 类型,则执行 props.class = normalizeClass(klass),对其 props.class 重新赋值。我们再看下 normalizeClass 方法:

export function normalizeClass(value: unknown): string {
  let res = ''
  // 是字符串 直接赋值
  if (isString(value)) {
    res = value
  } else if (isArray(value)) {
    // 是数组 则递归迭代再拼接
    for (let i = 0; i < value.length; i++) {
      const normalized = normalizeClass(value[i])
      if (normalized) {
        res += normalized + ' '
      }
    }
  } else if (isObject(value)) {
    // 是对象 则 for in 再拼接返回
    for (const name in value) {
      if (value[name]) {
        res += name + ' '
      }
    }
  }
  return res.trim()
}

根据 value 类型,如果是字符串则直接返回;如果是数组则递归迭代再拼接返回;如果是对象则迭代再拼接返回。由于当前 value 是对象 { red: true },所以此时的 resred

在这里插入图片描述

最终的结果:

在这里插入图片描述

总结

1)createVNode 核心是处理 shapeFlag 赋值,之后在 createBaseVNode 中又通过 shapeFlagtype 根据按位或运算,重新对 shapeFlag 赋值

2)h 函数本质上是对四个属性处理 childrenpropsshapeFlagtypeclass style 的增强

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值