image.png
当我们写出这样的代码之后,我们肯定不希望组件执行四次重新渲染,而是执行一次渲染。那就应该降低更新频率,对 effect 去重

组件多次更新属性只渲染一次

  1. const setupRenderEffect = (
  2. instance,
  3. initialVNode,
  4. container,
  5. anchor,
  6. parentSuspense,
  7. isSVG,
  8. optimized
  9. ) => {
  10. // create reactive effect for rendering
  11. // 每个组件都有一个effect, vue3 是组件及更新,数据变化会重新执行对应组件的effect
  12. instance.update = effect(function componentEffect() {
  13. // 如果没有被挂载,就是初次渲染
  14. if (!instance.isMounted) {
  15. let vnodeHook
  16. const { el, props } = initialVNode
  17. const { bm, m, parent } = instance
  18. // beforeMount hook
  19. if (bm) {
  20. invokeArrayFns(bm)
  21. }
  22. // onVnodeBeforeMount
  23. if ((vnodeHook = props && props.onVnodeBeforeMount)) {
  24. invokeVNodeHook(vnodeHook, parent, initialVNode)
  25. }
  26. if (
  27. __COMPAT__ &&
  28. isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
  29. ) {
  30. instance.emit('hook:beforeMount')
  31. }
  32. if (el && hydrateNode) {
  33. // vnode has adopted host node - perform hydration instead of mount.
  34. const hydrateSubTree = () => {
  35. if (__DEV__) {
  36. startMeasure(instance, `render`)
  37. }
  38. instance.subTree = renderComponentRoot(instance)
  39. if (__DEV__) {
  40. endMeasure(instance, `render`)
  41. }
  42. if (__DEV__) {
  43. startMeasure(instance, `hydrate`)
  44. }
  45. hydrateNode!(
  46. el as Node,
  47. instance.subTree,
  48. instance,
  49. parentSuspense,
  50. null
  51. )
  52. if (__DEV__) {
  53. endMeasure(instance, `hydrate`)
  54. }
  55. }
  56. if (isAsyncWrapper(initialVNode)) {
  57. (initialVNode.type).__asyncLoader!().then(
  58. // note: we are moving the render call into an async callback,
  59. // which means it won't track dependencies - but it's ok because
  60. // a server-rendered async wrapper is already in resolved state
  61. // and it will never need to change.
  62. () => !instance.isUnmounted && hydrateSubTree()
  63. )
  64. } else {
  65. hydrateSubTree()
  66. }
  67. } else {
  68. if (__DEV__) {
  69. startMeasure(instance, `render`)
  70. }
  71. const subTree = (instance.subTree = renderComponentRoot(instance))
  72. if (__DEV__) {
  73. endMeasure(instance, `render`)
  74. }
  75. if (__DEV__) {
  76. startMeasure(instance, `patch`)
  77. }
  78. patch(
  79. null,
  80. subTree,
  81. container,
  82. anchor,
  83. instance,
  84. parentSuspense,
  85. isSVG
  86. )
  87. if (__DEV__) {
  88. endMeasure(instance, `patch`)
  89. }
  90. initialVNode.el = subTree.el
  91. }
  92. // mounted hook
  93. if (m) {
  94. queuePostRenderEffect(m, parentSuspense)
  95. }
  96. // onVnodeMounted
  97. if ((vnodeHook = props && props.onVnodeMounted)) {
  98. const scopedInitialVNode = initialVNode
  99. queuePostRenderEffect(
  100. () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
  101. parentSuspense
  102. )
  103. }
  104. if (
  105. __COMPAT__ &&
  106. isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
  107. ) {
  108. queuePostRenderEffect(
  109. () => instance.emit('hook:mounted'),
  110. parentSuspense
  111. )
  112. }
  113. // activated hook for keep-alive roots.
  114. // #1742 activated hook must be accessed after first render
  115. // since the hook may be injected by a child keep-alive
  116. if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
  117. instance.a && queuePostRenderEffect(instance.a, parentSuspense)
  118. if (
  119. __COMPAT__ &&
  120. isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
  121. ) {
  122. queuePostRenderEffect(
  123. () => instance.emit('hook:activated'),
  124. parentSuspense
  125. )
  126. }
  127. }
  128. instance.isMounted = true
  129. if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
  130. devtoolsComponentAdded(instance)
  131. }
  132. // #2458: deference mount-only object parameters to prevent memleaks
  133. initialVNode = container = anchor = null as any
  134. } else {
  135. // 更新逻辑
  136. }
  137. }, scheduler.queueJob)
  138. }

组件更新会走 scheduler.queueJob

  1. export function queueJob(job) {
  2. if (
  3. (!queue.length ||
  4. !queue.includes(
  5. job,
  6. isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
  7. )) &&
  8. job !== currentPreFlushParentJob
  9. ) {
  10. const pos = findInsertionIndex(job)
  11. if (pos > -1) {
  12. queue.splice(pos, 0, job)
  13. } else {
  14. // 存在队列里
  15. queue.push(job)
  16. }
  17. // 执行队列
  18. queueFlush()
  19. }
  20. }
  21. // 执行一次就好
  22. function queueFlush() {
  23. // 如果不是正在更新中
  24. if (!isFlushing && !isFlushPending) {
  25. // 标识正在刷新
  26. isFlushPending = true
  27. // 等当前任务执行完毕,清空队列
  28. currentFlushPromise = resolvedPromise.then(flushJobs)
  29. }
  30. }
  31. const getId = (job) =>
  32. job.id == null ? Infinity : job.id
  33. // 清空任务
  34. function flushJobs(seen) {
  35. isFlushPending = false
  36. isFlushing = true
  37. flushPreFlushCbs(seen)
  38. // Sort queue before flush.
  39. // This ensures that:
  40. // 1. Components are updated from parent to child. (because parent is always
  41. // created before the child so its render effect will have smaller
  42. // priority number)
  43. // 2. If a component is unmounted during a parent component's update,
  44. // its update can be skipped.
  45. // 清空时 需要根据调用顺序 依次刷新, 先父后子
  46. queue.sort((a, b) => getId(a) - getId(b))
  47. try {
  48. for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
  49. const job = queue[flushIndex]
  50. jbo();
  51. }
  52. } finally {
  53. flushIndex = 0
  54. queue.length = 0 // 清空
  55. flushPostFlushCbs(seen)
  56. isFlushing = false
  57. }
  58. }

将当前effect存放到队列里,没存放的时候刷新队列,但是队列只能执行一次。。。这里没太懂,

默认两个元素比较

  1. // 创建一个 effect 让 render 执行
  2. const setupRenderEffect = (
  3. instance,
  4. initialVNode,
  5. container,
  6. anchor,
  7. parentSuspense,
  8. isSVG,
  9. optimized
  10. ) => {
  11. // create reactive effect for rendering
  12. instance.update = effect(function componentEffect() {
  13. // 没有被挂载就是初次渲染
  14. if (!instance.isMounted) {
  15. let proxyToUse = instance.proxy;
  16. // 执行render 他的返回值是 h 函数的执行结果, 就是组件的渲染内容
  17. /**
  18. * setup(props,context){
  19. * return (proxy) => {
  20. * return h('div', {}, 'hello world')
  21. * }
  22. * }
  23. */
  24. let subTree = instance.subTree = instance.render.call(proxyToUse,proxyToUse);
  25. // 用render 函数的返回值 继续渲染子树
  26. patch(
  27. null,
  28. subTree,
  29. container,
  30. anchor,
  31. instance,
  32. parentSuspense,
  33. isSVG
  34. )
  35. instance.isMounted = true
  36. // #2458: deference mount-only object parameters to prevent memleaks
  37. initialVNode = container = anchor = null
  38. } else {
  39. // 更新组件
  40. let proxyToUse = instance.proxy;
  41. // 新树
  42. const nextTree = instance.subTree = instance.render.call(proxyToUse,proxyToUse);
  43. // 旧树
  44. const prevTree = instance.subTree
  45. instance.subTree = nextTree
  46. patch(
  47. prevTree,
  48. nextTree,
  49. // parent may have changed if it's in a teleport
  50. // hostParentNode(prevTree.el!)!,
  51. // anchor may have changed if it's in a fragment
  52. getNextHostNode(prevTree),
  53. instance,
  54. parentSuspense,
  55. isSVG
  56. )
  57. }

更新的时候将组件返回的新旧两个 vnode tree 传入 patch 进行对比。会走元素的更新,因为组件执行返回的是我这个组件下有哪些 元素。

元素的更新

元素的更新依旧会走 patch

  1. const patch = (
  2. n1, // 老的虚拟节点
  3. n2, // 新的虚拟节点
  4. container,
  5. anchor = null,
  6. parentComponent = null,
  7. parentSuspense = null,
  8. isSVG = false,
  9. slotScopeIds = null,
  10. optimized = false
  11. ) => {
  12. // 如果类型不一致直接销毁老的节点树
  13. // 如果 两棵树的 type 不同,既不是相同类型的节点就删掉的老的节点。 例如 老的 div -》 新的 span
  14. if (n1 && !isSameVNodeType(n1, n2)) {
  15. /**
  16. * 删除老的节点之前需要获取到新的节点要插入到哪个节点的前面,就是获取到老的节点的下一个兄弟节点, 调用DOM API node.nextSibling获取, 如下:
  17. * <div>
  18. * <a href=""></a>
  19. * <p></p>
  20. * </div>
  21. * <div>
  22. * <span href=""></span>
  23. * <p></p>
  24. * </div>
  25. */
  26. anchor = getNextHostNode(n1)
  27. unmount(n1, parentComponent, parentSuspense, true)
  28. // 会挂载 n2 对应的内容,将anchor 传入
  29. n1 = null
  30. }
  31. const { type, ref, shapeFlag } = n2
  32. switch (type) {
  33. case Text:
  34. processText(n1, n2, container, anchor)
  35. break
  36. case Comment:
  37. processCommentNode(n1, n2, container, anchor)
  38. break
  39. case Static:
  40. if (n1 == null) {
  41. mountStaticNode(n2, container, anchor, isSVG)
  42. } else if (__DEV__) {
  43. patchStaticNode(n1, n2, container, isSVG)
  44. }
  45. break
  46. case Fragment:
  47. processFragment(
  48. n1,
  49. n2,
  50. container,
  51. anchor,
  52. parentComponent,
  53. parentSuspense,
  54. isSVG,
  55. slotScopeIds,
  56. optimized
  57. )
  58. break
  59. default:
  60. // 元素
  61. if (shapeFlag & ShapeFlags.ELEMENT) {
  62. processElement(
  63. n1,
  64. n2,
  65. container,
  66. anchor,
  67. parentComponent,
  68. parentSuspense,
  69. isSVG,
  70. slotScopeIds,
  71. optimized
  72. )
  73. } else if (shapeFlag & ShapeFlags.COMPONENT) { // 组件
  74. processComponent( // 处理组件
  75. n1,
  76. n2,
  77. container,
  78. anchor,
  79. parentComponent,
  80. parentSuspense,
  81. isSVG,
  82. slotScopeIds,
  83. optimized
  84. )
  85. }
  86. }
  87. }

新旧两个虚拟节点传入,首先判断类型是否一致,不一致就直接销毁老的节点,挂载新的节点。
挂载新节点需要知道将他插入到哪个位置,就需要获取老节点的下一个兄弟节点,将它插入到 这个兄弟节点之前。

  1. const getNextHostNode: NextFn = vnode => {
  2. return hostNextSibling((vnode.anchor || vnode.el)!)
  3. }

hostNextSibling 调用的是 DOM API node.nextSibling获取的

销毁节点就是将老的元素节点从 页面中 删除,调用 dom API parent.removeChild(child) ,如果销毁的是组件,还需要考虑组件的生命周期。

然后就走到了 processElement,
如果是元素类型不同,就会走初次渲染 mountElement,调用 DOM API parent.insertBefore(child, anchor || null),将它插入到参考节点的前面,
如果元素标签类型相同就会节点复用,需要对比,走 patchElement 。

  1. // 处理元素的渲染
  2. const processElement = (
  3. n1,
  4. n2,
  5. container,
  6. anchor,
  7. parentComponent,
  8. parentSuspense,
  9. isSVG,
  10. slotScopeIds,
  11. optimized
  12. ) => {
  13. isSVG = isSVG || (n2.type) === 'svg'
  14. // 初次渲染
  15. if (n1 == null) {
  16. mountElement(
  17. n2,
  18. container,
  19. anchor,
  20. parentComponent,
  21. parentSuspense,
  22. isSVG,
  23. slotScopeIds,
  24. optimized
  25. )
  26. } else {
  27. // 更新
  28. patchElement(
  29. n1,
  30. n2,
  31. parentComponent,
  32. parentSuspense,
  33. isSVG,
  34. slotScopeIds,
  35. optimized
  36. )
  37. }
  38. }

patchElement 就是对两颗树的对比。对比属性和儿子。

  1. const patchElement = (
  2. n1,
  3. n2,
  4. parentComponent,
  5. parentSuspense,
  6. isSVG,
  7. slotScopeIds,
  8. optimized
  9. ) => {
  10. // 节点复用
  11. const el = (n2.el = n1.el)
  12. // 拿到元素之后 更新属性 更新儿子
  13. let { patchFlag, dynamicChildren, dirs } = n2
  14. patchFlag |= n1.patchFlag & PatchFlags.FULL_PROPS
  15. // 老的属性
  16. const oldProps = n1.props || EMPTY_OBJ
  17. // 新的属性
  18. const newProps = n2.props || EMPTY_OBJ
  19. let vnodeHook
  20. if (__DEV__ && isHmrUpdating) {
  21. // HMR updated, force full diff
  22. patchFlag = 0
  23. optimized = false
  24. dynamicChildren = null
  25. }
  26. if (patchFlag > 0) {
  27. if (patchFlag & PatchFlags.FULL_PROPS) {
  28. // element props contain dynamic keys, full diff needed
  29. patchProps(
  30. el, // 更新哪个元素
  31. n2,
  32. oldProps,
  33. newProps,
  34. parentComponent,
  35. parentSuspense,
  36. isSVG
  37. )
  38. } else {
  39. // class
  40. // this flag is matched when the element has dynamic class bindings.
  41. if (patchFlag & PatchFlags.CLASS) {
  42. if (oldProps.class !== newProps.class) {
  43. hostPatchProp(el, 'class', null, newProps.class, isSVG)
  44. }
  45. }
  46. // style
  47. // this flag is matched when the element has dynamic style bindings
  48. if (patchFlag & PatchFlags.STYLE) {
  49. hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
  50. }
  51. if (patchFlag & PatchFlags.PROPS) {
  52. // if the flag is present then dynamicProps must be non-null
  53. const propsToUpdate = n2.dynamicProps!
  54. for (let i = 0; i < propsToUpdate.length; i++) {
  55. const key = propsToUpdate[i]
  56. const prev = oldProps[key]
  57. const next = newProps[key]
  58. if (
  59. next !== prev ||
  60. (hostForcePatchProp && hostForcePatchProp(el, key))
  61. ) {
  62. hostPatchProp(
  63. el,
  64. key,
  65. prev,
  66. next,
  67. isSVG,
  68. n1.children as VNode[],
  69. parentComponent,
  70. parentSuspense,
  71. unmountChildren
  72. )
  73. }
  74. }
  75. }
  76. }
  77. // text
  78. // This flag is matched when the element has only dynamic text children.
  79. if (patchFlag & PatchFlags.TEXT) {
  80. if (n1.children !== n2.children) {
  81. hostSetElementText(el, n2.children as string)
  82. }
  83. }
  84. } else if (!optimized && dynamicChildren == null) {
  85. // unoptimized, full diff
  86. patchProps(
  87. el,
  88. n2,
  89. oldProps,
  90. newProps,
  91. parentComponent,
  92. parentSuspense,
  93. isSVG
  94. )
  95. }
  96. const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
  97. if (dynamicChildren) {
  98. patchBlockChildren(
  99. n1.dynamicChildren!,
  100. dynamicChildren,
  101. el,
  102. parentComponent,
  103. parentSuspense,
  104. areChildrenSVG,
  105. slotScopeIds
  106. )
  107. if (__DEV__ && parentComponent && parentComponent.type.__hmrId) {
  108. traverseStaticChildren(n1, n2)
  109. }
  110. } else if (!optimized) {
  111. // full diff
  112. patchChildren(
  113. n1,
  114. n2,
  115. el,
  116. null,
  117. parentComponent,
  118. parentSuspense,
  119. areChildrenSVG,
  120. slotScopeIds,
  121. false
  122. )
  123. }
  124. }

1.新老属性对比。

  1. const patchProps = (
  2. el,
  3. vnode,
  4. oldProps,
  5. newProps,
  6. parentComponent,
  7. parentSuspense,
  8. isSVG
  9. ) => {
  10. // 如果新的老的不一样 就对比
  11. if (oldProps !== newProps) {
  12. // 循环用新的覆盖掉老的
  13. for (const key in newProps) {
  14. const next = newProps[key]
  15. const prev = oldProps[key]
  16. if (
  17. next !== prev ||
  18. (hostForcePatchProp && hostForcePatchProp(el, key))
  19. ) {
  20. hostPatchProp(
  21. el,
  22. key,
  23. prev,
  24. next,
  25. isSVG,
  26. vnode.children,
  27. parentComponent,
  28. parentSuspense,
  29. unmountChildren
  30. )
  31. }
  32. }
  33. if (oldProps !== EMPTY_OBJ) {
  34. // 老的有 新的没有 就删掉
  35. for (const key in oldProps) {
  36. if (!isReservedProp(key) && !(key in newProps)) {
  37. hostPatchProp(
  38. el,
  39. key,
  40. oldProps[key],
  41. null, // 新的 为 null 就删除
  42. isSVG,
  43. vnode.children,
  44. parentComponent,
  45. parentSuspense,
  46. unmountChildren
  47. )
  48. }
  49. }
  50. }
  51. }
  52. }

如果新的老的属性不一致就覆盖。
如果老的有,新的没有就 删除。

2.新老儿子对比

  1. /**
  2. * 老的有儿子 新的没有儿子
  3. * 新的有儿子 老的没有儿子
  4. * 新老都有儿子 新老都是文本
  5. */
  6. const patchChildren = (
  7. n1,
  8. n2,
  9. container,
  10. anchor,
  11. parentComponent,
  12. parentSuspense,
  13. isSVG,
  14. slotScopeIds,
  15. optimized = false
  16. ) => {
  17. const c1 = n1 && n1.children
  18. const prevShapeFlag = n1 ? n1.shapeFlag : 0
  19. const c2 = n2.children
  20. const { patchFlag, shapeFlag } = n2
  21. // fast path
  22. if (patchFlag > 0) {
  23. if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
  24. // this could be either fully-keyed or mixed (some keyed some not)
  25. // presence of patchFlag means children are guaranteed to be arrays
  26. patchKeyedChildren(
  27. c1,
  28. c2,
  29. container,
  30. anchor,
  31. parentComponent,
  32. parentSuspense,
  33. isSVG,
  34. slotScopeIds,
  35. optimized
  36. )
  37. return
  38. } else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
  39. // unkeyed
  40. patchUnkeyedChildren(
  41. c1,
  42. c2,
  43. container,
  44. anchor,
  45. parentComponent,
  46. parentSuspense,
  47. isSVG,
  48. slotScopeIds,
  49. optimized
  50. )
  51. return
  52. }
  53. }
  54. // 新的是文本,不管老的是啥
  55. if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
  56. // 老的是 n 个孩子,但是新的是文本,需要卸载
  57. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  58. unmountChildren(c1 , parentComponent, parentSuspense) // 如果c1 中包含组件会调用组件的销毁方法
  59. }
  60. // 新老都是文本的情况 就设置新的文本
  61. if (c2 !== c1) {
  62. hostSetElementText(container, c2)
  63. }
  64. } else {
  65. /**
  66. * 老:h('div', {style: {color: 'red'}}, [h('span', 'hello'), h('span', 'hello')])
  67. * 新:h('div', {style: {color: 'red'}}, h('span', 'hello'))
  68. */
  69. // 上一次是 数组
  70. if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  71. // 现在是数组
  72. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  73. // 当前是数组 之前是数组 diff算法 ***************************************
  74. patchKeyedChildren(
  75. c1,
  76. c2,
  77. container,
  78. anchor,
  79. parentComponent,
  80. parentSuspense,
  81. isSVG,
  82. slotScopeIds,
  83. optimized
  84. )
  85. } else {
  86. // 当前没有孩子 特殊情况 当前是 null 删除老的
  87. unmountChildren(c1 , parentComponent, parentSuspense, true)
  88. }
  89. } else {
  90. // prev children was text OR null 老的是文本
  91. // new children is array OR null 新的是数组
  92. // 把文本清空
  93. if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
  94. hostSetElementText(container, '')
  95. }
  96. // mount new if array 挂载新的数组
  97. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
  98. mountChildren(
  99. c2,
  100. container,
  101. anchor,
  102. parentComponent,
  103. parentSuspense,
  104. isSVG,
  105. slotScopeIds,
  106. optimized
  107. )
  108. }
  109. }
  110. }
  111. }

1.如果新的是文本,老的也是文本,就给元素设置新文本。el.textContent = text
2.如果新的文本,老的是数组,就需要卸载,卸载的时候将所有孩子传入 然后循环遍历 调用 unmount方法,parent.removeChild(child)

  1. // 循环卸载所有孩子
  2. const unmountChildren = (
  3. children,
  4. parentComponent,
  5. parentSuspense,
  6. doRemove = false,
  7. optimized = false,
  8. start = 0
  9. ) => {
  10. for (let i = start; i < children.length; i++) {
  11. unmount(children[i], parentComponent, parentSuspense, doRemove, optimized)
  12. }
  13. }

3.老的是数组,新的也是数组,走 diff 算法*
4.老的是数组,新的没有孩子,就卸载老的。
5.老的是文本,新的是数组,把老的文本清空,挂载新的

diff 算法

核心针对的是 元素的 diff。
当新老都有儿子且不是文本的情况下会走 diff 算法。

比较两个元素的 key 。
没有采用双指针,但是标识了两个children 的 开始 和结尾。 i 表示开始的位置,e1 表示c1 的结束位置,e2 表示c2 的结束位置。
image.png
从头开始比,两个 children 有一个比完了就不用比了,遇到不同的就停止。

  1. // 1. sync from start
  2. // (a b) c
  3. // (a b) d e
  4. while (i <= e1 && i <= e2) {
  5. const n1 = c1[i]
  6. const n2 = (c2[i] = optimized
  7. ? cloneIfMounted(c2[i])
  8. : normalizeVNode(c2[i]))
  9. // 如果类型相等就 递归 比较儿子
  10. if (isSameVNodeType(n1, n2)) {
  11. patch(
  12. n1,
  13. n2,
  14. container,
  15. null,
  16. parentComponent,
  17. parentSuspense,
  18. isSVG,
  19. slotScopeIds,
  20. optimized
  21. )
  22. } else {
  23. // 类型不同就停止
  24. break
  25. }
  26. i++
  27. }

image.png

  1. // 2. sync from end
  2. // a (b c)
  3. // d e (b c)
  4. while (i <= e1 && i <= e2) {
  5. const n1 = c1[e1]
  6. const n2 = (c2[e2] = optimized
  7. ? cloneIfMounted(c2[e2])
  8. : normalizeVNode(c2[e2]))
  9. if (isSameVNodeType(n1, n2)) {
  10. patch(
  11. n1,
  12. n2,
  13. container,
  14. null,
  15. parentComponent,
  16. parentSuspense,
  17. isSVG,
  18. slotScopeIds,
  19. optimized
  20. )
  21. } else {
  22. break
  23. }
  24. e1--
  25. e2--
  26. }

image.png
比对完还剩一个的情况就直接插入

  1. // 3. common sequence + mount
  2. // (a b)
  3. // (a b) c
  4. // i = 2, e1 = 1, e2 = 2
  5. // (a b)
  6. // c (a b)
  7. // i = 0, e1 = -1, e2 = 0
  8. if (i > e1) { // 老的少 新的多
  9. if (i <= e2) { // 表示有新增部分
  10. const nextPos = e2 + 1
  11. const anchor = nextPos < l2 ? (c2[nextPos]).el : parentAnchor
  12. while (i <= e2) {
  13. // 向后追加
  14. patch(
  15. null,
  16. (c2[i] = optimized
  17. ? cloneIfMounted(c2[i])
  18. : normalizeVNode(c2[i])),
  19. container,
  20. anchor,
  21. parentComponent,
  22. parentSuspense,
  23. isSVG,
  24. slotScopeIds,
  25. optimized
  26. )
  27. i++
  28. }
  29. }
  30. }

image.png
i > e1 就挂载

  1. // 4. common sequence + unmount
  2. // (a b) c
  3. // (a b)
  4. // i = 2, e1 = 2, e2 = 1
  5. // a (b c)
  6. // (b c)
  7. // i = 0, e1 = 0, e2 = -1
  8. else if (i > e2) { // 老的多 新的少
  9. while (i <= e1) { // 表示删除部分
  10. unmount(c1[i], parentComponent, parentSuspense, true)
  11. i++
  12. }
  13. }

组件更新整理

组件是如何更新的,自身属性数据变化,外界属性变化也要更新。

父给儿子传入属性 儿子是否要更新?更新一次 -> 组件本身需要更新属性

儿子使用了属性,属性变化了要不要更新?更新一次 -> 更新页面渲染

这个更新流程:
image.png
先更新父组件的属性,产生一个新的 vnode,然后走patch,类型是元素就走 processElement,新旧 vnode 元素类型相同,就会元素复用,有属性就走 patchProps,有children走patchChildren,两个children比较
新的文本,老的数组,卸载老的。
新的文本,老的文本,将新的文本插入覆盖老的。
新的数组,老的数组,就走 patchKeyChildren,也就是diff 算法。

children是组件,就走 组件的处理过程,n1 n2 都存在就是组件更新,走 updateComponent。组件复用,n2.component = n1.component, 然后看组件是否要更新,就看组件的属性和插槽等是否发生了变化。在更新之前要删除组件本身的更新,防止更新两次?这块没看懂。

大概意思是我父组件属性变化并且传给子组件,本身就会让他更新一次,然后子组件用到了这个属性,子组件也会让自己更新一次,这就是两次更新,所以需要删除 子组件本身的更新。

image.png

image.png