类似before mutation阶段mutation阶段也是遍历effectList,执行函数。这里执行的是commitMutationEffects

commitMutationEffects

代码如下:

  1. function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  2. // 遍历effectList
  3. while (nextEffect !== null) {
  4. const effectTag = nextEffect.effectTag;
  5. // 根据 ContentReset effectTag重置文字节点
  6. if (effectTag & ContentReset) {
  7. commitResetTextContent(nextEffect);
  8. }
  9. // 更新ref
  10. if (effectTag & Ref) {
  11. const current = nextEffect.alternate;
  12. if (current !== null) {
  13. commitDetachRef(current);
  14. }
  15. }
  16. // 根据 effectTag 分别处理
  17. const primaryEffectTag =
  18. effectTag & (Placement | Update | Deletion | Hydrating);
  19. switch (primaryEffectTag) {
  20. // 插入DOM
  21. case Placement: {
  22. commitPlacement(nextEffect);
  23. nextEffect.effectTag &= ~Placement;
  24. break;
  25. }
  26. // 插入DOM 并 更新DOM
  27. case PlacementAndUpdate: {
  28. // 插入
  29. commitPlacement(nextEffect);
  30. nextEffect.effectTag &= ~Placement;
  31. // 更新
  32. const current = nextEffect.alternate;
  33. commitWork(current, nextEffect);
  34. break;
  35. // SSR
  36. case Hydrating: {
  37. nextEffect.effectTag &= ~Hydrating;
  38. break;
  39. }
  40. // SSR
  41. case HydratingAndUpdate: {
  42. nextEffect.effectTag &= ~Hydrating;
  43. const current = nextEffect.alternate;
  44. commitWork(current, nextEffect);
  45. break;
  46. }
  47. // 更新DOM
  48. case Update: {
  49. const current = nextEffect.alternate;
  50. commitWork(current, nextEffect);
  51. break;
  52. }
  53. // 删除DOM
  54. case Deletion: {
  55. commitDeletion(root, nextEffect, renderPriorityLevel);
  56. break;
  57. }
  58. }
  59. nextEffect = nextEffect.nextEffect;
  60. }
  61. }

commitMutationEffects会遍历effectList,对每个执行如下三个操作:

  1. 根据ContentReset effectTag重置文字节点
  2. 更新ref
  3. 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating)

我们关注步骤三中的Placement | Update | DeletionHydrating作为服务端渲染相关,我们先不关注。

Fiber节点含有Placement effectTag,意味着该Fiber节点对应的DOM节点需要插入到页面中。

调用的方法为commitPlacement

你可以在看到commitPlacement源码

该方法所做的工作分为三步:

  1. 获取父级DOM节点。其中finishedWork为传入的Fiber节点
  1. const parentFiber = getHostParentFiber(finishedWork);
  2. // 父级DOM节点
  3. const parentStateNode = parentFiber.stateNode;
  1. 获取Fiber节点DOM兄弟节点
  1. const before = getHostSibling(finishedWork);
  1. 根据DOM兄弟节点是否存在决定调用parentNode.insertBeforeparentNode.appendChild执行DOM插入操作。

这是由于Fiber节点不只包括HostComponent,所以Fiber树和渲染的DOM树节点并不是一一对应的。要从Fiber节点找到DOM节点很可能跨层级遍历。

考虑如下例子:

  1. function Item() {
  2. return <li><li>;
  3. }
  4. function App() {
  5. return (
  6. <div>
  7. </div>
  8. )
  9. }
  10. ReactDOM.render(<App/>, document.getElementById('root'));

对应的Fiber树DOM树结构为:

  1. // Fiber树
  2. child child child child
  3. rootFiber -----> App -----> div -----> Item -----> li
  4. // DOM树
  5. #root ---> div ---> li

当在div的子节点Item前插入一个新节点p,即App变为:

  1. function App() {
  2. return (
  3. <div>
  4. <p></p>
  5. <Item/>
  6. </div>
  7. )
  8. }

对应的Fiber树DOM树结构为:

此时DOM节点 的兄弟节点为li,而Fiber节点 p对应的兄弟DOM节点为:

  1. fiberP.sibling.child

fiber p兄弟fiber Item子fiber li

Update effect

Fiber节点含有Update effectTag,意味着该Fiber节点需要更新。调用的方法为commitWork,他会根据Fiber.tag分别处理。

fiber.tagFunctionComponent,会调用commitHookEffectListUnmount。该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。

你可以在这里mutation阶段 - 图4 (opens new window)看到commitHookEffectListUnmount源码

所谓“销毁函数”,见如下例子:

  1. useLayoutEffect(() => {
  2. // ...一些副作用逻辑
  3. return () => {
  4. // ...这就是销毁函数
  5. }
  6. })

你不需要很了解useLayoutEffect,我们会在下一节详细介绍。你只需要知道在mutation阶段会执行useLayoutEffect的销毁函数。

HostComponent mutation

fiber.tagHostComponent,会调用commitUpdate

最终会在updateDOMProperties (opens new window)中将中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

  1. for (let i = 0; i < updatePayload.length; i += 2) {
  2. const propKey = updatePayload[i];
  3. const propValue = updatePayload[i + 1];
  4. // 处理 style
  5. if (propKey === STYLE) {
  6. setValueForStyles(domElement, propValue);
  7. // 处理 DANGEROUSLY_SET_INNER_HTML
  8. } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
  9. setInnerHTML(domElement, propValue);
  10. // 处理 children
  11. } else if (propKey === CHILDREN) {
  12. setTextContent(domElement, propValue);
  13. } else {
  14. // 处理剩余 props
  15. setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
  16. }
  17. }

Fiber节点含有Deletion effectTag,意味着该Fiber节点对应的DOM节点需要从页面中删除。调用的方法为commitDeletion

你可以在看到commitDeletion源码

  1. 递归调用Fiber节点及其子孙Fiber节点fiber.tagClassComponentcomponentWillUnmountmutation阶段 - 图9 (opens new window)生命周期钩子,从页面移除Fiber节点对应DOM节点
  2. 解绑ref

总结

从这节我们学到,mutation阶段会遍历effectList,依次执行commitMutationEffects。该方法的主要工作为“根据调用不同的处理函数处理Fiber