Vue에서 diff 알고리즘을 사용하는 방법

php中世界最好的语言
풀어 주다: 2018-06-12 14:25:57
원래의
2546명이 탐색했습니다.

이번에는 Vue에서 diff 알고리즘을 사용하는 방법과 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. children's 비교

  • oldVnode에만 하위 노드가 있는 경우 해당 노드를 삭제합니다.

  • vnode에만 하위 노드가 있는 경우 oldVnode가 텍스트인 경우 해당 하위 노드를 만듭니다. node, vnode.elm의 텍스트를 추가합니다. 빈 문자열로 설정되면

  • , updateChildren이 업데이트됩니다. 나중에 자세히 설명하겠습니다. 노드 또는 주석 노드, vnode.elm의 텍스트를 빈 String

  • updateChildren

이 부분의 초점은 여전히 ​​전체 알고리즘에 있습니다처음 4개 포인터, oldStart, oldEnd, newStart, newEnd, two 배열, 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)
  }
 }
로그인 후 복사

루프 비교의 여러 상황 및 처리(다음 ++ - 모두 인덱스의 ++를 참조함 -) 비교는 비교되는 노드 노드입니다. 비교는 엄격하지 않습니다. 이는 사실이 아닙니다. 전체 루프가 끝나지 않는 조건

oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

oldStart === newStart, oldStart++ newStart++

  1. oldEnd === newEnd, oldEnd-- newEnd--

  2. oldStart === newEnd, oldStart는 대기열의 끝에 삽입됩니다. oldStart++ newEnd--

  3. oldEnd === newStart, oldEnd는 대기열의 시작 부분에 삽입됩니다. oldEnd- - newStart++

  4. 남은 상황은 모두 이렇게 처리합니다. 간단히 말하면, 처리 후 newStart++

  5. newStart가 old에서 같은 것을 찾아 oldStart 앞으로 옮깁니다
.
  • 같은 것이 없으면 새로 만들어서 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)
  }

简单的说就是循环结束后,看四个指针中间的内容,old数组中和new数组中,多退少补而已

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

微信小程序开发中怎样实现搜索内容高亮功能

如何在项目中使用js中存储键值

위 내용은 Vue에서 diff 알고리즘을 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿