通过对数据响应式原理的分析,了解到当数据发生变化时会触发渲染watcher的回调函数,进而执行组件的更新

  1. updateComponent = () => {
  2. vm._update(vm._render(), hydrating)
  3. }
  4. new Watcher(vm, updateComponent, noop, {
  5. before () {
  6. if (vm._isMounted && !vm._isDestroyed) {
  7. callHook(vm, 'beforeUpdate')
  8. }
  9. }
  10. }, true /* isRenderWatcher */)

组件更新调用了vm._update方法
定义在src/core/instance/lifecycle.js中

  1. Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  2. const vm: Component = this
  3. const prevEl = vm.$el
  4. const prevVnode = vm._vnode
  5. const restoreActiveInstance = setActiveInstance(vm)
  6. vm._vnode = vnode
  7. // Vue.prototype.__patch__ is injected in entry points
  8. // based on the rendering backend used.
  9. if (!prevVnode) {
  10. // initial render
  11. vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  12. } else {
  13. // updates
  14. // 更新组件
  15. vm.$el = vm.__patch__(prevVnode, vnode)
  16. }
  17. restoreActiveInstance()
  18. // update __vue__ reference
  19. if (prevEl) {
  20. prevEl.__vue__ = null
  21. }
  22. if (vm.$el) {
  23. vm.$el.__vue__ = vm
  24. }
  25. // if parent is an HOC, update its $el as well
  26. if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  27. vm.$parent.$el = vm.$el
  28. }
  29. // updated hook is called by the scheduler to ensure that children are
  30. // updated in a parent's updated hook.
  31. }

调用patch函数,定义在src/core/vdom/patch.js中

  1. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  2. if (isUndef(vnode)) {
  3. if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
  4. return
  5. }
  6. let isInitialPatch = false
  7. const insertedVnodeQueue = []
  8. // 更新组件 oldVnode不为空且和vnode都是VNode类型 所有走到了else
  9. if (isUndef(oldVnode)) {
  10. // empty mount (likely as component), create new root element
  11. isInitialPatch = true
  12. createElm(vnode, insertedVnodeQueue)
  13. } else {
  14. const isRealElement = isDef(oldVnode.nodeType)
  15. // sameVnode判断它们是否是相同的VNode来决定走不同的更新逻辑
  16. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  17. // patch existing root node
  18. patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
  19. } else {
  20. if (isRealElement) {
  21. // mounting to a real element
  22. // check if this is server-rendered content and if we can perform
  23. // a successful hydration.
  24. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
  25. oldVnode.removeAttribute(SSR_ATTR)
  26. hydrating = true
  27. }
  28. if (isTrue(hydrating)) {
  29. if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
  30. invokeInsertHook(vnode, insertedVnodeQueue, true)
  31. return oldVnode
  32. } else if (process.env.NODE_ENV !== 'production') {
  33. warn(
  34. 'The client-side rendered virtual DOM tree is not matching ' +
  35. 'server-rendered content. This is likely caused by incorrect ' +
  36. 'HTML markup, for example nesting block-level elements inside ' +
  37. '<p>, or missing <tbody>. Bailing hydration and performing ' +
  38. 'full client-side render.'
  39. )
  40. }
  41. }
  42. // either not server-rendered, or hydration failed.
  43. // create an empty node and replace it
  44. oldVnode = emptyNodeAt(oldVnode)
  45. }
  46. // replacing existing element
  47. const oldElm = oldVnode.elm
  48. const parentElm = nodeOps.parentNode(oldElm)
  49. // create new node
  50. createElm(
  51. vnode,
  52. insertedVnodeQueue,
  53. // extremely rare edge case: do not insert if old element is in a
  54. // leaving transition. Only happens when combining transition +
  55. // keep-alive + HOCs. (#4590)
  56. oldElm._leaveCb ? null : parentElm,
  57. nodeOps.nextSibling(oldElm)
  58. )
  59. // update parent placeholder node element, recursively
  60. if (isDef(vnode.parent)) {
  61. let ancestor = vnode.parent
  62. const patchable = isPatchable(vnode)
  63. while (ancestor) {
  64. for (let i = 0; i < cbs.destroy.length; ++i) {
  65. cbs.destroy[i](ancestor)
  66. }
  67. ancestor.elm = vnode.elm
  68. if (patchable) {
  69. for (let i = 0; i < cbs.create.length; ++i) {
  70. cbs.create[i](emptyNode, ancestor)
  71. }
  72. // #6513
  73. // invoke insert hooks that may have been merged by create hooks.
  74. // e.g. for directives that uses the "inserted" hook.
  75. const insert = ancestor.data.hook.insert
  76. if (insert.merged) {
  77. // start at index 1 to avoid re-invoking component mounted hook
  78. for (let i = 1; i < insert.fns.length; i++) {
  79. insert.fns[i]()
  80. }
  81. }
  82. } else {
  83. registerRef(ancestor)
  84. }
  85. ancestor = ancestor.parent
  86. }
  87. }
  88. // destroy old node
  89. if (isDef(parentElm)) {
  90. removeVnodes([oldVnode], 0, 0)
  91. } else if (isDef(oldVnode.tag)) {
  92. invokeDestroyHook(oldVnode)
  93. }
  94. }
  95. }
  96. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  97. return vnode.elm
  98. }

sameVnode方法

  1. function sameVnode (a, b) {
  2. return (
  3. a.key === b.key && // key不相等则不同
  4. a.asyncFactory === b.asyncFactory && ( // 异步组件 判断asyncFactory是否相同
  5. ( // 同步组件 判断isComment data input类型等是否相同
  6. a.tag === b.tag &&
  7. a.isComment === b.isComment &&
  8. isDef(a.data) === isDef(b.data) &&
  9. sameInputType(a, b)
  10. ) || (
  11. isTrue(a.isAsyncPlaceholder) &&
  12. isUndef(b.asyncFactory.error)
  13. )
  14. )
  15. )
  16. }

根据新旧 vnode 是否为 sameVnode,会走到不同的更新逻辑

新旧节点不同

如果新旧 vnode 不同,那么更新的逻辑非常简单,它本质上是要替换已存在的节点,大致分为 3 步

创建新节点

  1. // replacing existing element
  2. const oldElm = oldVnode.elm
  3. const parentElm = nodeOps.parentNode(oldElm)
  4. // create new node
  5. createElm(
  6. vnode,
  7. insertedVnodeQueue,
  8. // extremely rare edge case: do not insert if old element is in a
  9. // leaving transition. Only happens when combining transition +
  10. // keep-alive + HOCs. (#4590)
  11. oldElm._leaveCb ? null : parentElm,
  12. nodeOps.nextSibling(oldElm)
  13. )

以当前旧节点为参考节点,创建新的节点,并插入到 DOM 中

更新父的占位符节点

  1. // update parent placeholder node element, recursively
  2. if (isDef(vnode.parent)) {
  3. // 当前Vnode的父的占位符节点
  4. let ancestor = vnode.parent
  5. const patchable = isPatchable(vnode)
  6. while (ancestor) {
  7. // 执行各个module的destory的钩子函数
  8. for (let i = 0; i < cbs.destroy.length; ++i) {
  9. cbs.destroy[i](ancestor)
  10. }
  11. ancestor.elm = vnode.elm
  12. // 当前占位符是一个可挂载的节点
  13. if (patchable) {
  14. // 执行各个module的create钩子函数
  15. for (let i = 0; i < cbs.create.length; ++i) {
  16. cbs.create[i](emptyNode, ancestor)
  17. }
  18. // #6513
  19. // invoke insert hooks that may have been merged by create hooks.
  20. // e.g. for directives that uses the "inserted" hook.
  21. const insert = ancestor.data.hook.insert
  22. if (insert.merged) {
  23. // start at index 1 to avoid re-invoking component mounted hook
  24. for (let i = 1; i < insert.fns.length; i++) {
  25. insert.fns[i]()
  26. }
  27. }
  28. } else {
  29. registerRef(ancestor)
  30. }
  31. ancestor = ancestor.parent
  32. }
  33. }

删除旧节点

  1. // destroy old node
  2. if (isDef(parentElm)) {
  3. removeVnodes([oldVnode], 0, 0)
  4. } else if (isDef(oldVnode.tag)) {
  5. invokeDestroyHook(oldVnode)
  6. }

把oldVnode从当前DOM树中删除,如果父节点存在则执行removeVnodes方法

  1. function removeVnodes (vnodes, startIdx, endIdx) {
  2. // 遍历待删除的vnodes做删除
  3. for (; startIdx <= endIdx; ++startIdx) {
  4. const ch = vnodes[startIdx]
  5. if (isDef(ch)) {
  6. if (isDef(ch.tag)) {
  7. removeAndInvokeRemoveHook(ch)
  8. invokeDestroyHook(ch)
  9. } else { // Text node
  10. // 调用平台的DOM API去把真正的DOM节点移除
  11. removeNode(ch.elm)
  12. }
  13. }
  14. }
  15. }
  16. // 从DOM中移除节点并执行module的remove钩子函数,并对它的子节点递归调用自身
  17. function removeAndInvokeRemoveHook (vnode, rm) {
  18. if (isDef(rm) || isDef(vnode.data)) {
  19. let i
  20. const listeners = cbs.remove.length + 1
  21. if (isDef(rm)) {
  22. // we have a recursively passed down rm callback
  23. // increase the listeners count
  24. rm.listeners += listeners
  25. } else {
  26. // directly removing
  27. rm = createRmCb(vnode.elm, listeners)
  28. }
  29. // recursively invoke hooks on child component root node
  30. if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
  31. removeAndInvokeRemoveHook(i, rm)
  32. }
  33. for (i = 0; i < cbs.remove.length; ++i) {
  34. cbs.remove[i](vnode, rm)
  35. }
  36. if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
  37. i(vnode, rm)
  38. } else {
  39. rm()
  40. }
  41. } else {
  42. removeNode(vnode.elm)
  43. }
  44. }
  45. // 执行module的destory钩子函数以及vnode的destory钩子函数,并对它的子vnode递归调用自身
  46. function invokeDestroyHook (vnode) {
  47. let i, j
  48. const data = vnode.data
  49. if (isDef(data)) {
  50. if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
  51. for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
  52. }
  53. if (isDef(i = vnode.children)) {
  54. for (j = 0; j < vnode.children.length; ++j) {
  55. invokeDestroyHook(vnode.children[j])
  56. }
  57. }
  58. }

beforeDestroy和destroyed这两个生命周期钩子函数就是在执行invokeDestroyHook过程中执行了vnode的destroy钩子函数
destroy定义在src/core/vdom/create-component.js中

  1. destroy (vnode: MountedComponentVNode) {
  2. const { componentInstance } = vnode
  3. if (!componentInstance._isDestroyed) {
  4. if (!vnode.data.keepAlive) {
  5. componentInstance.$destroy()
  6. } else {
  7. deactivateChildComponent(componentInstance, true /* direct */)
  8. }
  9. }
  10. }

当组件不是keepAlive时会执行componentInstance.$destroy()方法,然后就会执行beforeDestroy和Destroyed两个钩子函数

新旧节点相同

调用patchVnode方法,定义在src/core/vdom/patch.js中

  1. // 把新的vnode patch到旧的vnode上
  2. function patchVnode (
  3. oldVnode,
  4. vnode,
  5. insertedVnodeQueue,
  6. ownerArray,
  7. index,
  8. removeOnly
  9. ) {
  10. if (oldVnode === vnode) {
  11. return
  12. }
  13. if (isDef(vnode.elm) && isDef(ownerArray)) {
  14. // clone reused vnode
  15. vnode = ownerArray[index] = cloneVNode(vnode)
  16. }
  17. const elm = vnode.elm = oldVnode.elm
  18. if (isTrue(oldVnode.isAsyncPlaceholder)) {
  19. if (isDef(vnode.asyncFactory.resolved)) {
  20. hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
  21. } else {
  22. vnode.isAsyncPlaceholder = true
  23. }
  24. return
  25. }
  26. // reuse element for static trees.
  27. // note we only do this if the vnode is cloned -
  28. // if the new node is not cloned it means the render functions have been
  29. // reset by the hot-reload-api and we need to do a proper re-render.
  30. if (isTrue(vnode.isStatic) &&
  31. isTrue(oldVnode.isStatic) &&
  32. vnode.key === oldVnode.key &&
  33. (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  34. ) {
  35. vnode.componentInstance = oldVnode.componentInstance
  36. return
  37. }
  38. // 执行prepatch钩子函数
  39. let i
  40. const data = vnode.data
  41. if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  42. i(oldVnode, vnode)
  43. }
  44. const oldCh = oldVnode.children
  45. const ch = vnode.children
  46. if (isDef(data) && isPatchable(vnode)) {
  47. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  48. if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  49. }
  50. if (isUndef(vnode.text)) {
  51. if (isDef(oldCh) && isDef(ch)) {
  52. if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  53. } else if (isDef(ch)) {
  54. if (process.env.NODE_ENV !== 'production') {
  55. checkDuplicateKeys(ch)
  56. }
  57. if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
  58. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  59. } else if (isDef(oldCh)) {
  60. removeVnodes(oldCh, 0, oldCh.length - 1)
  61. } else if (isDef(oldVnode.text)) {
  62. nodeOps.setTextContent(elm, '')
  63. }
  64. } else if (oldVnode.text !== vnode.text) {
  65. nodeOps.setTextContent(elm, vnode.text)
  66. }
  67. if (isDef(data)) {
  68. if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  69. }
  70. }

执行prepatch钩子函数

  1. let i
  2. const data = vnode.data
  3. if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  4. i(oldVnode, vnode)
  5. }

当更新的vnode是一个组件vnode时会执行prepatch方法
定义在src/core/vdom/create-component.js中

  1. const componentVNodeHooks = {
  2. // 拿到新的vnode的组件配置以及组件实例,执行updateChildComponent
  3. prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  4. const options = vnode.componentOptions
  5. const child = vnode.componentInstance = oldVnode.componentInstance
  6. updateChildComponent(
  7. child,
  8. options.propsData, // updated props
  9. options.listeners, // updated listeners
  10. vnode, // new parent vnode
  11. options.children // new children
  12. )
  13. },
  14. }

updateChildComponent方法定义在src/core/instance/lifecycle.js中

  1. export function updateChildComponent (
  2. vm: Component,
  3. propsData: ?Object,
  4. listeners: ?Object,
  5. parentVnode: MountedComponentVNode,
  6. renderChildren: ?Array<VNode>
  7. ) {
  8. if (process.env.NODE_ENV !== 'production') {
  9. isUpdatingChildComponent = true
  10. }
  11. // determine whether component has slot children
  12. // we need to do this before overwriting $options._renderChildren.
  13. // check if there are dynamic scopedSlots (hand-written or compiled but with
  14. // dynamic slot names). Static scoped slots compiled from template has the
  15. // "$stable" marker.
  16. const newScopedSlots = parentVnode.data.scopedSlots
  17. const oldScopedSlots = vm.$scopedSlots
  18. const hasDynamicScopedSlot = !!(
  19. (newScopedSlots && !newScopedSlots.$stable) ||
  20. (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
  21. (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
  22. (!newScopedSlots && vm.$scopedSlots.$key)
  23. )
  24. // Any static slot children from the parent may have changed during parent's
  25. // update. Dynamic scoped slots may also have changed. In such cases, a forced
  26. // update is necessary to ensure correctness.
  27. const needsForceUpdate = !!(
  28. renderChildren || // has new static slots
  29. vm.$options._renderChildren || // has old static slots
  30. hasDynamicScopedSlot
  31. )
  32. vm.$options._parentVnode = parentVnode
  33. vm.$vnode = parentVnode // update vm's placeholder node without re-render
  34. if (vm._vnode) { // update child tree's parent
  35. vm._vnode.parent = parentVnode
  36. }
  37. vm.$options._renderChildren = renderChildren
  38. // update $attrs and $listeners hash
  39. // these are also reactive so they may trigger child update if the child
  40. // used them during render
  41. vm.$attrs = parentVnode.data.attrs || emptyObject
  42. vm.$listeners = listeners || emptyObject
  43. // update props
  44. if (propsData && vm.$options.props) {
  45. toggleObserving(false)
  46. const props = vm._props
  47. const propKeys = vm.$options._propKeys || []
  48. for (let i = 0; i < propKeys.length; i++) {
  49. const key = propKeys[i]
  50. const propOptions: any = vm.$options.props // wtf flow?
  51. props[key] = validateProp(key, propOptions, propsData, vm)
  52. }
  53. toggleObserving(true)
  54. // keep a copy of raw propsData
  55. vm.$options.propsData = propsData
  56. }
  57. // update listeners
  58. listeners = listeners || emptyObject
  59. const oldListeners = vm.$options._parentListeners
  60. vm.$options._parentListeners = listeners
  61. updateComponentListeners(vm, listeners, oldListeners)
  62. // resolve slots + force update if has children
  63. if (needsForceUpdate) {
  64. vm.$slots = resolveSlots(renderChildren, parentVnode.context)
  65. vm.$forceUpdate()
  66. }
  67. if (process.env.NODE_ENV !== 'production') {
  68. isUpdatingChildComponent = false
  69. }
  70. }

由于更新了 vnode,那么 vnode 对应的实例 vm 的一系列属性也会发生变化,包括占位符 vm.$vnode 的更新、slot 的更新,listeners 的更新,props 的更新等等

执行update钩子函数

  1. if (isDef(data) && isPatchable(vnode)) {
  2. // 会执行所有module的update钩子函数以及用户自定义的update钩子函数
  3. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  4. if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  5. }

完成patch过程

  1. const oldCh = oldVnode.children
  2. const ch = vnode.children
  3. if (isDef(data) && isPatchable(vnode)) {
  4. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  5. if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  6. }
  7. if (isUndef(vnode.text)) { // 不是文本节点则判断它们的子节点
  8. if (isDef(oldCh) && isDef(ch)) { // oldCh和ch都存在且不相同时使用updateChildren更新子节点
  9. if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  10. } else if (isDef(ch)) { // 只有ch存在表示旧节点不需要了
  11. if (process.env.NODE_ENV !== 'production') {
  12. checkDuplicateKeys(ch)
  13. }
  14. // 如果旧的节点时文本节点则先将节点的文本清除,然后通过addVnodes将ch批量插入到新节点elm下
  15. if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
  16. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  17. } else if (isDef(oldCh)) { // 只有oldCh存在表示更新的是空节点
  18. // 将旧的节点通过removeVnodes全部清除
  19. removeVnodes(oldCh, 0, oldCh.length - 1)
  20. } else if (isDef(oldVnode.text)) { // 当只有旧节点是文本节点时则清除其节点文本内容
  21. nodeOps.setTextContent(elm, '')
  22. }
  23. } else if (oldVnode.text !== vnode.text) { // vnode是个文本节点且新旧文本不相同则直接替换文本内容
  24. nodeOps.setTextContent(elm, vnode.text)
  25. }

执行postpatch钩子函数

  1. if (isDef(data)) {
  2. if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  3. }

updateChildren

在整个 pathVnode 过程中,最复杂的就是 updateChildren 方法

  1. function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  2. let oldStartIdx = 0
  3. let newStartIdx = 0
  4. let oldEndIdx = oldCh.length - 1
  5. let oldStartVnode = oldCh[0]
  6. let oldEndVnode = oldCh[oldEndIdx]
  7. let newEndIdx = newCh.length - 1
  8. let newStartVnode = newCh[0]
  9. let newEndVnode = newCh[newEndIdx]
  10. let oldKeyToIdx, idxInOld, vnodeToMove, refElm
  11. // removeOnly is a special flag used only by <transition-group>
  12. // to ensure removed elements stay in correct relative positions
  13. // during leaving transitions
  14. const canMove = !removeOnly
  15. if (process.env.NODE_ENV !== 'production') {
  16. checkDuplicateKeys(newCh)
  17. }
  18. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  19. if (isUndef(oldStartVnode)) {
  20. oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
  21. } else if (isUndef(oldEndVnode)) {
  22. oldEndVnode = oldCh[--oldEndIdx]
  23. } else if (sameVnode(oldStartVnode, newStartVnode)) {
  24. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  25. oldStartVnode = oldCh[++oldStartIdx]
  26. newStartVnode = newCh[++newStartIdx]
  27. } else if (sameVnode(oldEndVnode, newEndVnode)) {
  28. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  29. oldEndVnode = oldCh[--oldEndIdx]
  30. newEndVnode = newCh[--newEndIdx]
  31. } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
  32. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
  33. canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  34. oldStartVnode = oldCh[++oldStartIdx]
  35. newEndVnode = newCh[--newEndIdx]
  36. } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
  37. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  38. canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  39. oldEndVnode = oldCh[--oldEndIdx]
  40. newStartVnode = newCh[++newStartIdx]
  41. } else {
  42. if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
  43. idxInOld = isDef(newStartVnode.key)
  44. ? oldKeyToIdx[newStartVnode.key]
  45. : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
  46. if (isUndef(idxInOld)) { // New element
  47. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  48. } else {
  49. vnodeToMove = oldCh[idxInOld]
  50. if (sameVnode(vnodeToMove, newStartVnode)) {
  51. patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
  52. oldCh[idxInOld] = undefined
  53. canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
  54. } else {
  55. // same key but different element. treat as new element
  56. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
  57. }
  58. }
  59. newStartVnode = newCh[++newStartIdx]
  60. }
  61. }
  62. if (oldStartIdx > oldEndIdx) {
  63. refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
  64. addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  65. } else if (newStartIdx > newEndIdx) {
  66. removeVnodes(oldCh, oldStartIdx, oldEndIdx)
  67. }
  68. }

例子

  1. <template>
  2. <div id="app">
  3. <div>
  4. <ul>
  5. <li v-for="item in items" :key="item.id">{{ item.val }}</li>
  6. </ul>
  7. </div>
  8. <button @click="change">change</button>
  9. </div>
  10. </template>
  11. <script>
  12. export default {
  13. name: 'App',
  14. data() {
  15. return {
  16. items: [
  17. {id: 0, val: 'A'},
  18. {id: 1, val: 'B'},
  19. {id: 2, val: 'C'},
  20. {id: 3, val: 'D'}
  21. ]
  22. }
  23. },
  24. methods: {
  25. change() {
  26. this.items.reverse().push({id: 4, val: 'E'})
  27. }
  28. }
  29. }
  30. </script>

当点击change按钮去改变数据时最终会执行到updateChildren去更新li部分的列表数据

更新过程

image.png
image.png
image.png
image.png
image.png
image.png