Vue 3 의 VDOM 개선 에 대한 자세 한 설명

8024 단어 Vue3VDOM
머리말
vue-next 는 virtual dom 의 patch 업데이트 에 대해 일련의 최적화 를 했 고 컴 파일 할 때 block 을 추가 하여 vdom 간 의 대비 횟수 를 줄 였 으 며,또한 hoisted 의 조작 은 메모리 비용 을 줄 였 다.본 고 는 자신 에 게 보 여주 고 지식 적 인 기록 을 하 며 잘못 이 있 으 면 아낌없이 가르쳐 주 십시오.
VDOM
VDOM 의 개념 은 쉽게 말 하면 js 대상 으로 실제 DOM 트 리 를 모 의 하 는 것 이다.MV**의 구조 로 인해 실제 DOM 트 리 는 데이터(Vue2.x 중의 data)의 변화 에 따라 달라 져 야 합 니 다.이런 변 화 는 다음 과 같은 몇 가지 측면 일 수 있 습 니 다.
  • v-if
  • v-for
  • 동적 props(예:class,@click)
  • 서브 노드 의 변화
  • 등등
  • Vue 프레임 워 크 는 사실 매우 단일 합 니 다.사용자 가 데 이 터 를 바 꿀 때 DOM 트 리 를 정확하게 업데이트 하 는 방법 은 바로 그 핵심 적 인 VDOM 의 patch 와 diff 알고리즘 입 니 다.
    Vue 2.x 의 방법
    Vue 2.x 에서 데이터 가 바 뀌 면 모든 노드 에 패 치 와 diff 작업 을 해 야 합 니 다.다음 DOM 구조 와 같이:
    
    <div>
     <span class="header">I'm header</span>
     <ul>
      <li>     li</li>
      <li v-for="item in mutableItems" :key="item.key"> {{ item.desc }}</li>
     </ul>
    </div>
    
    첫 번 째 mount 노드 에서 실제 DOM 을 생 성 합 니 다.그 다음 에 만약 에...
    
    mutableItems.push({
     key: 'asdf',
     desc: 'a new li item'
    })
    
    예상 되 는 결 과 는 페이지 에 새로운 li 요소 가 나타 나 는 것 입 니 다.내용 은 a new li item 입 니 다.Vue 2.x 에 서 는 patch 를 통 해 ul 요소 에 대응 하 는 vnode 의 children 에 대해 diff 작업 을 합 니 다.구체 적 인 조작 은 여기 서 깊이 연구 하지 않 지만 이 조작 은 모든 li 에 대응 하 는 vnode 를 비교 해 야 합 니 다.
    모자라다
    바로 2.x 버 전의 diff 작업 은 모든 요 소 를 옮 겨 다 녀 야 하기 때문에 이 예 에는 span 과 첫 번 째 li 요소 가 포함 되 어 있 습 니 다.그러나 이 두 요 소 는 정적 이 고 비교 할 필요 가 없습니다.데이터 가 아무리 변 하 더 라 도 정적 요 소 는 더 이상 변경 되 지 않 습 니 다.vue-next 는 컴 파일 할 때 이러한 조작 을 최적화 시 켰 습 니 다.즉,Block 입 니 다.
    Block
    위 템 플 릿 에 들 어가 vue-next 에서 생 성 된 렌 더 링 함 수 는 다음 과 같 습 니 다.
    
    const _Vue = Vue
    const { createVNode: _createVNode } = _Vue
    
    const _hoisted_1 = _createVNode("span", { class: "header" }, "I'm header", -1 /* HOISTED */)
    const _hoisted_2 = _createVNode("li", null, "     li", -1 /* HOISTED */)
    
    return function render(_ctx, _cache) {
     with (_ctx) {
      const { createVNode: _createVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, toDisplayString: _toDisplayString } = _Vue
    
      return (_openBlock(), _createBlock(_Fragment, null, [
       _hoisted_1,
       _createVNode("ul", null, [
        _hoisted_2,
        (_openBlock(true), _createBlock(_Fragment, null, _renderList(state.mutableItems, (item) => {
         return (_openBlock(), _createBlock("li", { key: item.key }, _toDisplayString(item.desc), 1 /* TEXT */))
        }), 128 /* KEYED_FRAGMENT */))
       ])
      ], 64 /* STABLE_FRAGMENT */))
     }
    }
    
    
    우 리 는 openBlock 과 createBlock 방법 을 호출 한 것 을 볼 수 있 습 니 다.이 두 가지 방법의 코드 실현 도 간단 합 니 다.
    
    const blockStack: (VNode[] | null)[] = []
    let currentBlock: VNode[] | null = null
    let shouldTrack = 1
    // openBlock
    export function openBlock(disableTracking = false) {
     blockStack.push((currentBlock = disableTracking ? null : []))
    }
    export function createBlock(
     type: VNodeTypes | ClassComponent,
     props?: { [key: string]: any } | null,
     children?: any,
     patchFlag?: number,
     dynamicProps?: string[]
    ): VNode {
     // avoid a block with patchFlag tracking itself
     shouldTrack--
     const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
     shouldTrack++
     // save current block children on the block vnode
     vnode.dynamicChildren = currentBlock || EMPTY_ARR
     // close block
     blockStack.pop()
     currentBlock = blockStack[blockStack.length - 1] || null
     // a block is always going to be patched, so track it as a child of its
     // parent block
     if (currentBlock) {
      currentBlock.push(vnode)
     }
     return vnode
    }
    
    더 자세 한 설명 은 원본 코드 의 주석 을 보십시오.매우 상세 하 게 써 서 이해 하기 쉽 습 니 다.이 안에 openBlock 은 블록 을 초기 화 하 는 것 입 니 다.createBlock 은 현재 컴 파일 된 내용 에 블록 을 만 드 는 것 입 니 다.이 줄 의 코드 는 vnode.dynamicChildren=currentBlock||EMPTY 입 니 다.ARR 은 동적 인 하위 노드 를 수집 하 는 것 입 니 다.컴 파일 할 때 실행 되 는 함 수 를 다시 볼 수 있 습 니 다.
    
    // createVNode
    function _createVNode(
     type: VNodeTypes | ClassComponent,
     props: (Data & VNodeProps) | null = null,
     children: unknown = null,
     patchFlag: number = 0,
     dynamicProps: string[] | null = null
    ) {
     /**
      *      
     **/
    
     // 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.
     if (
      shouldTrack > 0 &&
      currentBlock &&
      // 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.
      patchFlag !== PatchFlags.HYDRATE_EVENTS &&
      (patchFlag > 0 ||
       shapeFlag & ShapeFlags.SUSPENSE ||
       shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
       shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
     ) {
      currentBlock.push(vnode)
     }
    }
    
    
    상기 함 수 는 템 플 릿 을 ast 로 컴 파일 한 후에 호출 된 VNode 를 생 성 하 는 함수 이기 때문에 patchFlag 이라는 표지 가 있 습 니 다.동적 노드 이 고 이때 Block 이 열 리 면 노드 를 Block 에 넣 습 니 다.그러면 createBlock 이 돌아 오 는 VNode 에 dynamic Children 이 있 습 니 다.
    지금까지 본 논문 에서 사례 를 통 해 템 플 릿 컴 파일 과 render 함수 가 실 행 된 후에 최적화 된 후에 다음 과 같은 구조의 vnode 를 생 성 했다.
    
    const result = {
     type: Symbol(Fragment),
     patchFlag: 64,
     children: [
      { type: 'span', patchFlag: -1, ...},
      {
       type: 'ul',
       patchFlag: 0,
       children: [
        { type: 'li', patchFlag: -1, ...},
        {
         type: Symbol(Fragment),
         children: [
          { type: 'li', patchFlag: 1 ...},
          { type: 'li', patchFlag: 1 ...}
         ]
        }
       ]
      }
     ],
     dynamicChildren: [
      {
       type: Symbol(Fragment),
       patchFlag: 128,
       children: [
        { type: 'li', patchFlag: 1 ...},
        { type: 'li', patchFlag: 1 ...}
       ]
      }
     ]
    }
    
    이상 의 result 는 완전 하지 않 지만,우 리 는 잠시 이러한 속성 에 만 관심 을 가 집 니 다.result.children 의 첫 번 째 요 소 는 span,patchFlag=-1 이 고 result 는 dynamic Children 배열 이 있 습 니 다.그 안에 두 개의 동적 li 만 포함 되 어 있 습 니 다.나중에 데 이 터 를 바 꾸 면 새로운 vnode.dynamic Children 에는 세 번 째 li 요소 가 있 습 니 다.
    patch
    patch 부분 도 별로 차이 가 없 는데 vnode 의 type 에 따라 서로 다른 patch 작업 을 수행 하 는 것 입 니 다.
    
    function patchElement(n1, n2) {
     let { dynamicChildren } = n2
     //      
    
     if (dynamicChildren) {
      patchBlockChildren (
       n1.dynamicChildren!,
       dynamicChildren,
       el,
       parentComponent,
       parentSuspense,
       areChildrenSVG
      )
     } else if (!optimized) {
      // full diff
      patchChildren(
       n1,
       n2,
       el,
       null,
       parentComponent,
       parentSuspense,
       areChildrenSVG
      )
     }
    }
    
    
    dynamic Children 이 있 으 면 vue 2.x 버 전의 diff 작업 이 patchBlock Children()로 바 뀌 고 인 자 는 dynamic Children 만 있 습 니 다.즉,정적 으로 diff 작업 을 하지 않 습 니 다.vue-next patch 에 dynamic Children 이 없 으 면 완전한 diff 작업 을 하고 주석 에 적 힌 full diff 의 후속 코드 를 입력 합 니 다.
    엔 딩
    본 고 는 코드 의 실현 차원 에 대해 깊이 설명 하지 않 았 다.첫째,자신의 실력 이 좋 지 않 아서 아직도 소스 코드 를 읽 고 있 기 때문이다.둘째,저 는 개인 적 으로 소스 코드 를 읽 을 때 사소한 부분 에서 착안 하면 안 된다 고 생각 합 니 다.그 다음 에 서 서 서 도 를 보면 각 부분의 역할 을 알 게 된 후에 생각 을 가지 고 소스 코드 를 읽 으 면 얻 을 수 있 는 것 이 더 많 을 것 입 니 다.
    Vue 3 에서 VDOM 에 대한 개선 사항 을 자세히 설명 하 는 이 글 은 여기까지 입 니 다.더 많은 Vue 3 VDOM 내용 은 저희 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 읽 어 주시 기 바 랍 니 다.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기