>  기사  >  웹 프론트엔드  >  vue의 diff 알고리즘 지식 포인트 요약

vue의 diff 알고리즘 지식 포인트 요약

亚连
亚连원래의
2018-05-28 11:26:331064검색

이 글은 Vue의 diff 알고리즘에 대한 관련 지식 포인트를 요약하여 공유합니다. 관심 있는 친구들은 이를 참고할 수 있습니다.

Virtual dom

diff 알고리즘은 먼저 diff의 객체가 가상 dom이라는 개념을 명확히 해야 하며 실제 dom을 업데이트하는 것은 diff 알고리즘의 결과입니다

Vnode 기본 클래스

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data && data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }

이 부분은 코드는 주로 업데이트를 위한 것입니다. diff 알고리즘의 특정 diff 속성의 의미를 알아두면 좋습니다. 물론 vnode 인스턴스를 더 잘 이해할 수도 있습니다

전체 프로세스

핵심 기능은 패치 기능입니다

  • isUndef 판단(정의되지 않았거나 null인지 여부)

  • // 빈 마운트(컴포넌트일 가능성이 높음), 새 루트 요소 생성createElm(vnode, 삽입된VnodeQueue) 여기에서 생성된 노드가 하나씩 삽입되지 않은 것을 확인할 수 있습니다. 하나이지만 통합 일괄 처리를 위해 큐에 넣습니다

  • 핵심 함수 sameVnode

function sameVnode (a, b) {
 return (
  a.key === b.key && (
   (
    a.tag === b.tag &&
    a.isComment === b.isComment &&
    isDef(a.data) === isDef(b.data) &&
    sameInputType(a, b)
   ) || (
    isTrue(a.isAsyncPlaceholder) &&
    a.asyncFactory === b.asyncFactory &&
    isUndef(b.asyncFactory.error)
   )
  )
 )
}

두 노드의 키, 태그(라벨), 데이터를 직접 비교하는 외부 비교 함수입니다. 여기서는 VNodeData를 참조하고 입력 유형을 직접 비교합니다.

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}

이것은 두 노드에 추가 비교 값이 있는지 확인하고 그렇지 않으면 직접 교체됩니다

교체 프로세스는 주로 createElm 함수이고 다른 하나는 oldVNode를 파괴하는 것입니다

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }

간단한 방법으로 삽입 프로세스는 노드의 유형을 결정하고 별도로 호출하는 것입니다

createComponent(자식이 있는지 확인한 후 재귀적으로 호출)

createComment

createTextNode

생성 후 삽입 기능을 사용하세요

그 후에는 다음을 수행해야 합니다. hydrate 함수를 사용하여 가상 dom과 실제 dom을 매핑합니다

function insert (parent, elm, ref) {
  if (isDef(parent)) {
   if (isDef(ref)) {
    if (ref.parentNode === parent) {
     nodeOps.insertBefore(parent, elm, ref)
    }
   } else {
    nodeOps.appendChild(parent, elm)
   }
  }
 }

core function

 function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  if (oldVnode === vnode) {
   return
  }

  const elm = vnode.elm = oldVnode.elm

  if (isTrue(oldVnode.isAsyncPlaceholder)) {
   if (isDef(vnode.asyncFactory.resolved)) {
    hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
   } else {
    vnode.isAsyncPlaceholder = true
   }
   return
  }

  if (isTrue(vnode.isStatic) &&
   isTrue(oldVnode.isStatic) &&
   vnode.key === oldVnode.key &&
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }

  let i
  const data = vnode.data
  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }

  const oldCh = oldVnode.children
  const ch = vnode.children
  if (isDef(data) && isPatchable(vnode)) {
   for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
   if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  }
  if (isUndef(vnode.text)) {
   if (isDef(oldCh) && isDef(ch)) {
    if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
   } else if (isDef(ch)) {
    if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
   } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
   } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, &#39;&#39;)
   }
  } else if (oldVnode.text !== vnode.text) {
   nodeOps.setTextContent(elm, vnode.text)
  }
  if (isDef(data)) {
   if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  }
 }

const el = vnode.el = oldVnode.el 이는 vnode.el이 현재 실제 dom을 참조하도록 하는 매우 중요한 단계입니다. el이 수정되면 vnode.el이 동기적으로 변경됩니다.

  1. 두 참조가 일치하는지 비교하세요

  2. asyncFactory가 그 이후에 무엇을 하는지 모르기 때문에 이해하기 어렵습니다.

  3. 정적 노드가 동일하면 키를 비교하지 않습니다. 다시 렌더링되고 componentInstance가 직접 복사됩니다(여기서 한 번 명령이 적용됩니다)

  4. vnode가 텍스트 노드 또는 주석 노드이지만 vnode.text != oldVnode.text인 경우 텍스트만 업데이트하면 됩니다. vnode.elm의 내용

  5. 어린이 비교

  • oldVnode에만 하위 노드가 있는 경우 해당 노드를 삭제하세요.

  • vnode에만 하위 노드가 있으면 해당 하위 노드를 생성하세요. 텍스트 노드, vnode.elm의 텍스트를 설정합니다. 빈 문자열인 경우

  • , updateChildren이 업데이트됩니다. 이에 대해서는 나중에 자세히 설명하겠습니다.

  • oldVnode와 vnode 모두 하위 노드가 없지만 oldVnode가 텍스트 노드인 경우 또는 주석 노드, vnode.elm의 텍스트를 빈 문자 String

updateChildren

이 부분의 초점은 여전히 ​​전체 알고리즘에 있습니다

처음 4개 포인터, oldStart, oldEnd, newStart, newEnd, 두 개의 어레이, oldVnode, Vnode.

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  let oldStartIdx = 0
  let newStartIdx = 0
  let oldEndIdx = oldCh.length - 1
  let oldStartVnode = oldCh[0]
  let oldEndVnode = oldCh[oldEndIdx]
  let newEndIdx = newCh.length - 1
  let newStartVnode = newCh[0]
  let newEndVnode = newCh[newEndIdx]
  let oldKeyToIdx, idxInOld, vnodeToMove, refElm

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
   if (isUndef(oldStartVnode)) {
    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
   } else if (isUndef(oldEndVnode)) {
    oldEndVnode = oldCh[--oldEndIdx]
   } else if (sameVnode(oldStartVnode, newStartVnode)) {
    patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
    oldStartVnode = oldCh[++oldStartIdx]
    newStartVnode = newCh[++newStartIdx]
   } else if (sameVnode(oldEndVnode, newEndVnode)) {
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
    oldStartVnode = oldCh[++oldStartIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
    patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
    canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
    oldEndVnode = oldCh[--oldEndIdx]
    newStartVnode = newCh[++newStartIdx]
   } else {
    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    idxInOld = isDef(newStartVnode.key)
     ? oldKeyToIdx[newStartVnode.key]
     : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
    if (isUndef(idxInOld)) { // New element
     createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
    } else {
     vnodeToMove = oldCh[idxInOld]
     if (sameVnode(vnodeToMove, newStartVnode)) {
      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
      oldCh[idxInOld] = undefined
      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
     } else {
      // same key but different element. treat as new element
      createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
     }
    }
    newStartVnode = newCh[++newStartIdx]
   }
  }
  if (oldStartIdx > oldEndIdx) {
   refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
   addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  } else if (newStartIdx > newEndIdx) {
   removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }

루프 비교의 여러 상황 및 처리(다음 ++ --모두 인덱스의 ++ 참조) 비교는 비교되는 노드 노드입니다. 약어는 엄격하지 않습니다. 이는 사실이 아닙니다. , oldEnd-- newEnd--

    oldStart === newEnd, oldStart는 대기열의 끝에 삽입됩니다. oldStart++ newEnd--
  1. oldEnd === newStart, oldEnd는 대기열의 시작 부분에 삽입됩니다. oldEnd -- newStart++
  2. 남은 상황은 모두 이렇게 처리합니다. 간단히 말하면, 처리 후 newStart++
  3. newStart가 old에서 같은 것을 찾아 oldStart 앞으로 옮깁니다.

  4. 같은 것이 없으면 하나 만들어서 oldStart 이전에 넣으세요

    루프가 끝난 후에도 완료되지 않습니다
  • 완성되기까지는 아직 판단 기간이 있습니다

    if (oldStartIdx > oldEndIdx) {
       refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
       addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
      } else if (newStartIdx > newEndIdx) {
       removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
      }
  • 간단히 말하면 , 루프가 끝난 후, 이전 배열과 새 배열에서 4개의 포인터 사이의 내용을 살펴보세요. 더 많이 반환하고 더 적게 구성하세요
  • 위 내용은 제가 모두를 위해 정리한 내용입니다. 앞으로도 다들.

  • 관련글 :

컴포넌트 자체의 라벨에 스타일 클래스를 추가하는 Angular5 방식

크로스 도메인 요청 구현을 위한 Vue-cli 개발 환경 방식

Vue-cli webpack 모바일 단말 상세 설명 자동화된 빌드 렘 문제

위 내용은 vue의 diff 알고리즘 지식 포인트 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.