Vue3源码梳理:运行时之基于h函数生成vnode的内部流程

发布时间:2023年12月17日

VNode 节点类型

  • 对于vnode而言,具备很多节点类型
  • vue源码中patch函数switch处理包含了好几种类型,常见类型如下
    • Text:文本节点
    • Comment:注释节点
    • Static:静态dom节点
    • Fragment:包含多个根节点的模板被表示为一个片段 fragment
    • ELEMENT:DOM 节点
    • COMPONENT:组件
    • TELEPORT:新的内置组件
    • SUSPENSE:新的内置组件

h函数源码解析

1 )使用 h 函数,示例demo程序

<script src='../../dist/vue.global.js'></script>

<div id='app'></div>

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

2 )对源码进行debug, 进入h函数

// Actual implementation
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
  const l = arguments.length
  if (l === 2) {
    if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
      // single vnode without props
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren])
      }
      // props without children
      return createVNode(type, propsOrChildren)
    } else {
      // omit props
      return createVNode(type, null, propsOrChildren)
    }
  } else {
    if (l > 3) {
      children = Array.prototype.slice.call(arguments, 2)
    } else if (l === 3 && isVNode(children)) {
      children = [children]
    }
    return createVNode(type, propsOrChildren, children)
  }
}

h 函数需要三个参数: type, propsOrChildren, children

  • 注意第二个参数,propsOrChildren 是一个对象,它可以是props也可以是children
  • 内部是基于传入的长度和类型来判断的,先长度(先基于2来判断的)后类型
  • 最终返回 createVNode
  • h函数本身只是对用户传递的参数的处理,其本质是 createVNode
  • 使得 createVNode调用时,更加的方便

3 ) createVNode 源码


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
  }

  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.
  if (props) {
    // for reactive or proxy objects, we need to clone it to enable mutation.
    props = guardReactiveProps(props)!
    let { class: klass, style } = props
    if (klass && !isString(klass)) {
      props.class = normalizeClass(klass)
    }
    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)
    ? 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
  )
}
  • 其本质上触发的是 _createVNode,进入它,有6个参数
  • type, props, children, patchFlag, dynamicProps, isBlockNode,我们主要关注其中三个参数
    • type
    • props
    • children
  • 代码往下走,看下 isVNode 函数,判断比较简单
    • return value ? value.__v_isVNode === true: false
    • 就是根据value的属性来的
  • 之后在判断是否是class
  • 在之后判断 props,这里执行
    • guardReactiveProps(props) 解析props的逻辑暂时不去管它
      • vue会有class和style的增强,这块先不去管它
  • 之后走到一个比较复杂的三目运算 shapeFlag
  • 它本身是一个枚举类,定义了很多类型
  • 代码继续执行,直到 return createBaseVNode

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
}
  • 进入这个函数
    • type, props, children, patchFlag, dynamicProps, shapeFlag, isBlockNode, needFullChildrenNormalization
    • 接下来,创建 vnode对象,包含
      • __v_isVNode
    • 这时候构建出了一个初始的vnode对象
      • 初始化很多属性,我们只需要关注对我们有用的
  • 继续执行,到 normalizeChildren(vnode, children)
    • 这个函数里面涉及到一个 进位符 & 和 按位或赋值 |=
    • |= 这里是按位或运算
    • 这里展开下:
      • 10进制的1转换成二进制是: 01,
      • 10(2) === 2(10) 括号里面是进制
      • 在vue的运算里,其实他们都是32位的
    • 32位是指有32个比特位
      • 00000000 00000000 00000000 00000000
    • 二进制的1是:
      • 00000000 00000000 00000000 00000001
      • 当前调试debug的flag的值,10进制是1,也是如上表示
    • 二进制的8是:
      • 00000000 00000000 00000000 00001000
    • 上述1和8执行或运算(有一个1则是1),得到
      • 00000000 00000000 00000000 00001001

总结下

  • h函数本质上是处理一个参数的问题
  • 核心代码是在 _createVNode 中进行的
  • 里面生成vnode的核心方法,做了一件重要的事情是构建了一个 shapeFlag
  • 第一次构建的时候,它的flag是ELEMENT类型
  • 接下来return 了 createBaseVNode 函数
  • 它根据 type, props, childrenshapeFlag 生成了一个 vnode 节点
  • 通过按位或运算,来改变flag的值,重新赋值给 shapeFlag
  • 最终 return vnode 对象
文章来源:https://blog.csdn.net/Tyro_java/article/details/135048772
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。