头图

What is Block?

Block is a special vnode , which is used to store an extra dynamicChildren 99096be15cf8bc68fc997be15cf8bc68fc9 compared to the normal vnode dynamic node.

What is a dynamic node?观察vnodechildren中的第vnodechildren是动态的,第二个vnode class is dynamic, and the two vnode are dynamic nodes. Dynamic nodes will have a patchFlag attribute, which is used to indicate which attributes of the node are dynamic.

 const vnode = {
  type: 'div',
  children: [
    { type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
    { type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS },
    { type: 'span', children: 'foo' }
  ]
}

As Block , all its descendant dynamic nodes will be collected into dynamicChildren (child dynamic elements of descendants will also be collected into dynamicChildren ).

 const vnode = {
  type: 'div',
  children: [
    { type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
    { type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS },
    { type: 'span', children: 'foo' }
  ],
  dynamicChildren: [
    { type: 'span', children: ctx.foo, patchFlag: PatchFlags.TEXT },
    { type: 'span', children: 'foo', props: { class: normalizeClass(cls) }, patchFlag: PatchFlags.CLASS }
  ]
}

Which nodes will serve as Blocks?

The root node in the template, the node with v-for , v-if/v-else-if/v-else will be treated as Block . The following example:

SFC Playground

在这里插入图片描述

Collection of dynamicChildren

Observe the compiled code of tempalte , you will find that before creating Block a openBlock function will be executed.

 // 一个block栈用于存储
export const blockStack: (VNode[] | null)[] = []
// 一个数组,用于存储动态节点,最终会赋给dynamicChildren
export let currentBlock: VNode[] | null = null

export function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null : []))
}

openBlock中, disableTracking ee30ae6851895f48c8daca289dc2ea75---为truecurrentBlock null ;否则创建一个新的Array and assign it to currentBlock , and push to blockStack .

Look again createBlock , createBlock call a setupBlock method.

 export function createBlock(
  type: VNodeTypes | ClassComponent,
  props?: Record<string, any> | null,
  children?: any,
  patchFlag?: number,
  dynamicProps?: string[]
): VNode {
  return setupBlock(
    createVNode(
      type,
      props,
      children,
      patchFlag,
      dynamicProps,
      true /* isBlock: prevent a block from tracking itself */
    )
  )
}

setupBlock Receive a vnode parameter.

 function setupBlock(vnode: VNode) {
  // isBlockTreeEnabled > 0时,将currentBlock赋值给vnode.dynamicChildren
  // 否则置为null
  vnode.dynamicChildren =
    isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
  // 关闭block
  closeBlock()
  // 父block收集子block
  // 如果isBlockTreeEnabled > 0,并且currentBlock不为null,将vnode放入currentBlock中
  if (isBlockTreeEnabled > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  // 返回vnode
  return vnode
}

closeBlock :

 export function closeBlock() {
  // 弹出栈顶block
  blockStack.pop()
  // 将currentBlock设置为父block
  currentBlock = blockStack[blockStack.length - 1] || null
}

Before understanding the collection process of dynamicChildren , we should first understand that the creation order of nesting vnode is performed from the inside out. like:

 export default defineComponent({
  render() {
    return createVNode('div', null, [
      createVNode('ul', null, [
        createVNode('li', null, [
          createVNode('span', null, 'foo')
        ])
      ])
    ])
  }
})

vnode为: span -> li -> ul -> div

Before each creation of Block , you need to call openBlock to create a new array and assign it to currentBlock , and put it on the stack blockStack createBlockcreateBlock中会先vnodevnode参数传递给setupBlock .

When creating vnode , if certain conditions are met, vnode will be collected into currentBlock .

 // 收集当前动态节点到currentBlock中
if (
  isBlockTreeEnabled > 0 &&
  // 避免收集自己
  !isBlockNode &&
  // 存在parent block
  currentBlock &&
  // vnode.patchFlag需要大于0或shapeFlag中存在ShapeFlags.COMPONENT
  // patchFlag的存在表明该节点需要修补更新。
  // 组件节点也应该总是打补丁,因为即使组件不需要更新,它也需要将实例持久化到下一个 vnode,以便以后可以正确卸载它
  (vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
  vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
  currentBlock.push(vnode)
}

setupBlock中, currentBlock vnode.dynamicChildrencloseBlock关闭block (弹出blockStack栈顶元素, currentBlock blockStackblock的父block ), and then collect vnode into the parent block .

Example

In order to clear the collection process of dynamicChildren , we continue the analysis with an example.

 <template>
  <div>
    <span v-for="item in data">{{ item }}</span>
    <ComA :count="count"></ComA>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
const data = reactive([1, 2, 3])
const count = ref(0)
</script>

The above example, the code generated by the compiler after compilation is as follows. SFC Playground

 import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString, resolveComponent as _resolveComponent, createVNode as _createVNode } from "vue"

import { ref, reactive } from 'vue'

const __sfc__ = {
  __name: 'App',
  setup(__props) {

    const data = reactive([1, 2, 3])
    const count = ref(0)

    return (_ctx, _cache) => {
      const _component_ComA = _resolveComponent("ComA")

      return (_openBlock(), _createElementBlock("div", null, [
        (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(data, (item) => {
          return (_openBlock(), _createElementBlock("span", null, _toDisplayString(item), 1 /* TEXT */))
        }), 256 /* UNKEYED_FRAGMENT */)),
        _createVNode(_component_ComA, { count: count.value }, null, 8 /* PROPS */, ["count"])
      ]))
    }
  }

}
__sfc__.__file = "App.vue"
export default __sfc__

When the rendering function (the rendering function here is the return value of setup ) is executed, the execution flow is as follows:

  1. Execute _openBlock() to create a new array (call it div-block ), and push to the top of the stack blockStack
  2. Execute _openBlock(true) , because the parameter is true , so it will not create a new array, but assign null push currentBlock push to blockStack top of stack
  3. _renderList_renderList会遍历---b346fda5c0882b48c54cf1836307fc15 data ,并执行第二个renderItem参数, (item) => { ... }
  4. item 1 ,执行---cfbbf48c92d4c1dde28a3f48998f8683 renderItem ,执行---87b9d1704ba6245abb7233378ff1c0e3 _openBlock()span1-block ), and push to blockStack top of the stack. At this time blockStack , currentBlock status is as follows:
    在这里插入图片描述
  5. _createElementBlock("span", null, _toDisplayString(item), 1 /* TEXT */)_createElementBlock中会先createBaseVNode 3d4223f1624ea3dbe5d8016af972a80e---创建vnode ,在创建---44a04096e3a04626a1b220e67afb4635 vnode It is a block vnode ( isBlockNode parameter is true ), so it will not be collected in currentBlock
  6. After creating vnode , execute setupBlock and assign currentBlock to vnode.dynamicChildren .
  7. Execute closeBlock() , pop the top element of blcokStack currentBlock , and point blcokStack to the last element in ---d4b16b5b400013fcbd0a---. As shown below:
    在这里插入图片描述
  8. Since currentBlock is null at this time, skip currentBlock.push(vnode) .
  9. item = 2、item = 3 , the process is the same as 4-7 . When item = 3 , the status after block is created is as follows:
    在这里插入图片描述
  10. At this point, list rendered, and then _createElementBlock(_Fragment) is called.
  11. _createElementBlock的过程中, isBlockNode参数true currentBlock null ,所以不会被currentBlock collection
  12. setupBlockEMPTY_ARR (空数组) vnode.dynamicChildren ,并调用closeBlock()元素,使currentBlcok points to the latest top stack element. Since currentBlock is not null at this time, so execute currentBlock.push(vnode)
    在这里插入图片描述
  13. _createVNode(_component_ComA)vnode中, vnode.patchFlag === PatchFlag.PROPSvnodecurrentBlock中。
    在这里插入图片描述
  14. Execute _createElementBlock('div') . Create vnode first, because isBlockNode is true , so it will not be collected in currentBlock .
  15. Execute setupBlock() and assign currentBlock to vnode.dynamicChildren . Then execute closeBlock() to pop the top element of the stack. At this time, the length of blockStack is 0, so currentBlock will point to null
    在这里插入图片描述

The final generated vnode :

 {
  type: "div",
  children:
    [
      {
        type: Fragment,
        children: [{
          type: "span",
          children: "1",
          patchFlag: PatchFlag.TEXT,
          dynamicChildren: [],
        },
          {
            type: "span",
            children: "2",
            patchFlag: PatchFlag.TEXT,
            dynamicChildren: [],
          },
          {
            type: "span",
            children: "3",
            patchFlag: PatchFlag.TEXT,
            dynamicChildren: [],
          }],
        patchFlag: PatchFlag.UNKEYED_FRAGMENT,
        dynamicChildren: []
      },
      {
        type: ComA,
        children: null,
        patchFlag: PatchFlag.PROPS,
        dynamicChildren: null
      }
    ]
  ,
  patchFlag:0,
  dynamicChildren: [
    {
      type: Fragment,
      children: [{
        type: "span",
        children: "1",
        patchFlag: PatchFlag.TEXT,
        dynamicChildren: [],
      },
        {
          type: "span",
          children: "2",
          patchFlag: PatchFlag.TEXT,
          dynamicChildren: [],
        },
        {
          type: "span",
          children: "3",
          patchFlag: PatchFlag.TEXT,
          dynamicChildren: [],
        }],
      patchFlag: PatchFlag.UNKEYED_FRAGMENT,
      dynamicChildren: []
    },
    {
      type: ComA,
      children: null,
      patchFlag: PatchFlag.PROPS,
      dynamicChildren: null
    }
  ]
}

The role of Block

If you understand the Diff process, you should know that during the Diff process, a comparison is made even if the vnode has not changed. Block的比较, Block中的动态节点都会被收集到dynamicChildren中, Block between patch can directly compare the nodes in dynamicChildren , reducing the comparison between non-dynamic nodes.

Block patch时,会patchBlockChildren方法dynamicChildren patch

 const patchElement = (
  n1: VNode,
  n2: VNode,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean
) => {
  // ...
  let { patchFlag, dynamicChildren, dirs } = n2

  if (dynamicChildren) {
    patchBlockChildren(
      n1.dynamicChildren!,
      dynamicChildren,
      el,
      parentComponent,
      parentSuspense,
      areChildrenSVG,
      slotScopeIds
    )
    if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
      traverseStaticChildren(n1, n2)
    }
  } else if (!optimized) {
    patchChildren(
      n1,
      n2,
      el,
      null,
      parentComponent,
      parentSuspense,
      areChildrenSVG,
      slotScopeIds,
      false
    )
  }
  
  // ...
}

patchElement中如果新节点dynamicChildren ,说明此时新节点是个Block ,那么会patchBlockChildren dynamicChildren patchoptimized 420f92b546b3d40af423903cb345f7f4---为---4ac7d862012798afcd71a85213957dc7 false patchChildrenpatchChildren patchKeyedChildren/patchUnkeyedChildren do Diff .

 const patchBlockChildren: PatchBlockChildrenFn = (
  oldChildren,
  newChildren,
  fallbackContainer,
  parentComponent,
  parentSuspense,
  isSVG,
  slotScopeIds
) => {
  for (let i = 0; i < newChildren.length; i++) {
    const oldVNode = oldChildren[i]
    const newVNode = newChildren[i]
    // 确定父容器
    const container =
      oldVNode.el &&
      (oldVNode.type === Fragment ||
        !isSameVNodeType(oldVNode, newVNode) ||
        oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
        ? hostParentNode(oldVNode.el)!
        : fallbackContainer
    patch(
      oldVNode,
      newVNode,
      container,
      null,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      true
    )
  }
}

Summarize

Block is a means of performance optimization in vue3 . Block vnode ,它与普通vnode ,多出了一个dynamicChildren中Saves all dynamic nodes of Block children. Block to perform patch can directly compare dynamicChildren with dynamic nodes in patch comparison between static nodes.

The creation process of Block :

  1. Before creating a Block node, you need to call the openBlcok method, create a new array and assign it to blockStack currentBlock , and push The top of the stack for blockStack .
  2. In the process of creating vnode , if some conditions are met, the dynamic node will be placed in currentBlock .
  3. After the node is created, it is passed in setupBlock as a parameter. setupBlock中, currentBlock复制给vnode.dynamicChildren ,并调用closeBlcok ,弹出---f10c431a5157c2b40a8dcb08556f5e96 blockStack元素, And make currentBlock point to the latest top stack element. Finally, if currentBlock is not empty at this time, collect ---822cc7ada8990ac9a63d6b04c0a490f5 vnode into currentBlock .

MAXLZ
9 声望17 粉丝