Vue源码

64 阅读2分钟

vue2

响应式原理

graph LR
Object.defineProperty --> Object;
Object --> getter --收集依赖--> Dep;
Object --> setter --触发更新--> Dep;
Object.defineProperty --> Array;
Array --> 遍历元素;
Array --> 拦截变异方法;
Dep --> Watcher;
subgraph 依赖
Watcher --> 渲染Watcher --> 更新组件;
Watcher --> watch --异步--> 回调函数;
Watcher --> computed --同步--> getter函数;
end

更新原理

  • patch
graph TD
A(patch) --> B{oldVnode?};
B --yes--> C1(createElm);
B --no--> C2(patchVnode);
  • patchVnode
graph LR
patchVnode -- oldCh--> removeVnodes;
patchVnode -- ch--> addVnodes;
patchVnode -- oldCh,ch--> updateChildren;
  • updateChildren
/**
 * diff 过程:
 *   diff 优化:做了四种假设,假设新老节点开头结尾有相同节点的情况,一旦命中假设,就避免了一次循环,以提高执行效率
 *             如果不幸没有命中假设,则执行遍历,从老节点中找到新开始节点
 *             找到相同节点,则执行 patchVnode,然后将老节点移动到正确的位置
 *   如果老节点先于新节点遍历结束,则剩余的新节点执行新增节点操作
 *   如果新节点先于老节点遍历结束,则剩余的老节点执行删除操作,移除这些老节点
 */
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

  // removeOnly是一个特殊的标志,仅由 <transition-group> 使用,以确保被移除的元素在离开转换期间保持在正确的相对位置
  const canMove = !removeOnly

  if (process.env.NODE_ENV !== 'production') {
    // 检查新节点的 key 是否重复
    checkDuplicateKeys(newCh)
  }

  // 遍历新老两组节点,只要有一组遍历完(开始索引超过结束索引)则跳出循环
  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)) {
      // 老开始节点和新开始节点是同一个节点,执行 patch
      patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
      // patch 结束后老开始和新开始的索引分别加 1
      oldStartVnode = oldCh[++oldStartIdx]
      newStartVnode = newCh[++newStartIdx]
    } else if (sameVnode(oldEndVnode, newEndVnode)) {
      // 老结束和新结束是同一个节点,执行 patch
      patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
      // patch 结束后老结束和新结束的索引分别减 1
      oldEndVnode = oldCh[--oldEndIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
      // 老开始和新结束是同一个节点,执行 patch
      patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
      // 处理被 transtion-group 包裹的组件时使用
      canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
      // patch 结束后老开始索引加 1,新结束索引减 1
      oldStartVnode = oldCh[++oldStartIdx]
      newEndVnode = newCh[--newEndIdx]
    } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
      // 老结束和新开始是同一个节点,执行 patch
      patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
      canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
      // patch 结束后,老结束的索引减 1,新开始的索引加 1
      oldEndVnode = oldCh[--oldEndIdx]
      newStartVnode = newCh[++newStartIdx]
    } else {
      // 如果上面的四种假设都不成立,则通过遍历找到新开始节点在老节点中的位置索引

      // 找到老节点中每个节点 key 和 索引之间的关系映射 => oldKeyToIdx = { key1: idx1, ... }
      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)) {
          // 如果这两个节点是同一个,则执行 patch
          patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
          // patch 结束后将该老节点置为 undefined
          oldCh[idxInOld] = undefined
          canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
        } else {
          // 最后这种情况是,找到节点了,但是发现两个节点不是同一个节点,则视为新元素,执行创建
          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(oldCh, oldStartIdx, oldEndIdx)
  }
}

vue3

响应式原理

  • Object/Array
graph LR
Object/Array --> Proxy;
Proxy--> set --add/set--> A1(trigger);
Proxy ---> get --key--> A2(track);
Proxy ---> deleteProperty --delete--> A1(trigger);
Proxy ---> ownKeys --ITERATE_KEY/length--> A2(track);
Proxy ---> has --key--> A2(track);
  • Map/Set
graph LR
Map/Set --Map--> get方法 --key--> A2(track);
Map/Set --Map--> set方法 --add/set--> A1(trigger);
Map/Set --Map,Set--> has方法 --key--> A2(track);
Map/Set --Set--> add方法 --add--> A1(trigger);
Map/Set --Map,Set--> delete方法 --delete--> A1(trigger);
Map/Set --Map,Set--> clear方法 --clear--> A1(trigger);
Map/Set --Map,Set--> forEach方法 --ITERATE_KEY--> A2(track);
Map/Set --Map,Set--> keys,values,entries,Symbol.iterator方法 --MAP_KEY_ITERATE_KEY/ITERATE_KEY--> A2(track);
  • track: 收集依赖
graph LR
A2(track) --dep--> B2(activeEffect)
  • trigger:触发更新
graph LR
A1(trigger) --> B1-1(clear) --> 所有依赖;
A1(trigger) --key--> B1-2(add);
A1(trigger) --key--> B1-3(delete);
A1(trigger) --key--> B1-4(set);
B1-2(add) --Object---> C1-1(ITERATE_KEY);
B1-2(add) --Array---> C1-2(length);
B1-2(add) --Map---> C1-3(MAP_KEY_ITERATE_KEY);
B1-3(delete) --Array---> C1-2(length);
B1-3(delete) --Map---> C1-3(MAP_KEY_ITERATE_KEY);
B1-4(set) --Map---> C1-3(MAP_KEY_ITERATE_KEY)

总体流程

graph LR
A1(createApp) --> B1(mount) --> C1(createVNode) --> D1(render);
graph LR
A1(render) --> B1(patch);
B1(patch) --文本--> C1(processText);
B1(patch) --注释--> C2(processCommentNode);
B1(patch) --片段--> C3(processFragment);
B1(patch) --元素--> C4(processElement);
B1(patch) --组件--> C5(processComponent);
B1(patch) --TELEPORT/SUSPENSE--> C6(Comp.process);
graph LR
A1(processComponent) --> B1(mountComponent);
A1(processComponent) --> B2(updateComponent);
B1(mountComponent) --> C1(createComponentInstance) --> D1(setupComponent) --> E1(setupRenderEffect) --> C2(update);
B2(updateComponent) --> C2(update);
C2(update) --> F1(patch);
graph LR
A1(setupComponent) --> B1(validateComponentName) --> C1(validateDirectiveName) --> D1(createSetupContext) --> E1(handleSetupResult) --> F1(finishComponentSetup) --> G1(compile) --> H1(applyOptions);
graph LR
A1(processElement) --> B1(mountElement);
A1(processElement) --> B2(patchElement);
B1(mountElement) --TEXT_CHILDREN--> C1(hostSetElementText);
B1(mountElement) --ARRAY_CHILDREN--> C2(mountChildren);
C2(mountChildren) --遍历--> D1(patch);
B2(patchElement) --> C3(patchBlockChildren) --遍历--> D1(patch);
B2(patchElement) --> C4(patchChildren);
C4(patchChildren) --KEYED_FRAGMENT--> C5(patchKeyedChildren);
C4(patchChildren) --UNKEYED_FRAGMENT--> C6(patchUnkeyedChildren) --遍历--> D1(patch);
graph LR
A1(processText) --> B1(hostCreateText) --> C1(hostInsert);
A1(processText) --> B2(hostSetText);