若排除其它的操作项,每一次workLoop都有performUnitOfWork,每一个work有 beginWork和completeWork,可以将workLoop简单看成:

  1. ----beginWork
  2. ----------beginWork
  3. ----------------beginWork
  4. ----------------completeWork
  5. ----------------beginWork
  6. ----------------completeWork
  7. ----------------beginWork
  8. ----------------------beginWork
  9. ----------------------------beginWork
  10. ----------------------------completeWork
  11. ----------------------------beginWork
  12. ----------------------------completeWork
  13. ----------------------------beginWork
  14. ----------------------------------beginWork
  15. .........省略...
  16. ----------------------------------completeWork
  17. ----------------------------completeWork
  18. ----------------------completeWork
  19. ----------------completeWork
  20. ----------completeWork
  21. ----completeWork

beginWork是创FiberNode,completeWork结束FiberNode。当beginWork返回null,标志着这个节点没有子元素,可以使用completeWork完成work。

  1. function performUnitOfWork(workInProgress) {
  2. //...
  3. next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
  4. //...
  5. if (next === null) {
  6. // If this doesn't spawn new work, complete the current work.
  7. next = completeUnitOfWork(workInProgress);
  8. }
  9. //...
  10. return next;
  11. }

completeUnitOfWork

尝试完成当前的work,然后移动到下一个sibling。如果没有更多的sibling fiber,返回到parent fiber。

  1. function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
  2. while (true) {
  3. // The current, flushed, state of this fiber is the alternate.
  4. const current = workInProgress.alternate;
  5. const returnFiber = workInProgress.return;
  6. const siblingFiber = workInProgress.sibling;
  7. if ((workInProgress.effectTag & Incomplete) === NoEffect) {
  8. // 完成当前fiber.
  9. nextUnitOfWork = workInProgress;// 以便回滚
  10. if (enableProfilerTimer) {
  11. if (workInProgress.mode & ProfileMode) { startProfilerTimer(workInProgress); }
  12. nextUnitOfWork = completeWork(current, workInProgress,nextRenderExpirationTime);
  13. if (workInProgress.mode & ProfileMode) {// 假设我们没有错误,更新渲染持续时间
  14. stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
  15. }
  16. } else {
  17. nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime);
  18. }
  19. stopWorkTimer(workInProgress);
  20. resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
  21. if (nextUnitOfWork !== null) {
  22. // Completing this fiber spawned new work. Work on that next.
  23. return nextUnitOfWork;
  24. }
  25. // 将effect记录到当前fiber的父级
  26. if (returnFiber !== null && (returnFiber.effectTag & Incomplete) === NoEffect
  27. ) {
  28. if (returnFiber.firstEffect === null) {
  29. returnFiber.firstEffect = workInProgress.firstEffect;
  30. }
  31. if (workInProgress.lastEffect !== null) {
  32. if (returnFiber.lastEffect !== null) {
  33. returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
  34. }
  35. returnFiber.lastEffect = workInProgress.lastEffect;
  36. }
  37. // 当前fiber有effect,给上一个effect添加nextEffect,形成effect链表
  38. const effectTag = workInProgress.effectTag;
  39. if (effectTag > PerformedWork) {
  40. if (returnFiber.lastEffect !== null) {
  41. returnFiber.lastEffect.nextEffect = workInProgress;
  42. } else {
  43. returnFiber.firstEffect = workInProgress;
  44. }
  45. returnFiber.lastEffect = workInProgress;//记录最后一个
  46. }
  47. }
  48. if (siblingFiber !== null) {
  49. return siblingFiber;
  50. } else if (returnFiber !== null) {
  51. // If there's no more work in this returnFiber. Complete the returnFiber.
  52. workInProgress = returnFiber;
  53. continue;
  54. } else { return null; // We've reached the root. }
  55. } else { //出错暂时处理 }
  56. }
  57. return null;
  58. }

completeUnitOfWork 可分为 complete Work 和 建立 Effect链表。

completeWork

此时fiber只是一个fiberNode,在complete Work中完成dom Node。

  1. function completeWork(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. renderExpirationTime: ExpirationTime,
  5. ): Fiber | null {
  6. const newProps = workInProgress.pendingProps;
  7. switch (workInProgress.tag) {
  8. case IndeterminateComponent:break;
  9. case LazyComponent:break;
  10. case SimpleMemoComponent:
  11. case FunctionComponent:
  12. break;
  13. case ClassComponent: {
  14. const Component = workInProgress.type;
  15. if (isLegacyContextProvider(Component)) {
  16. popLegacyContext(workInProgress);
  17. }
  18. break;
  19. }
  20. case HostRoot: {
  21. // 代码见下面的HostRoot
  22. break;
  23. }
  24. case HostComponent: {
  25. // 代码见下面的HostComponent
  26. break;
  27. }
  28. case HostText: {
  29. // 代码见下面的HostText
  30. break;
  31. }
  32. case ForwardRef:break;
  33. case SuspenseComponent: {
  34. //此处不做讲解,省略代码...
  35. break;
  36. }
  37. case Fragment:break;
  38. case Mode:break;
  39. case Profiler:break;
  40. case HostPortal:
  41. popHostContainer(workInProgress);
  42. updateHostContainer(workInProgress);
  43. break;
  44. case ContextProvider:
  45. popProvider(workInProgress);// Pop provider fiber
  46. break;
  47. case ContextConsumer:break;
  48. case MemoComponent:break;
  49. case IncompleteClassComponent: {
  50. // Same as class component case. I put it down here so that the tags are
  51. // sequential to ensure this switch is compiled to a jump table.
  52. const Component = workInProgress.type;
  53. if (isLegacyContextProvider(Component)) {
  54. popLegacyContext(workInProgress);
  55. }
  56. break;
  57. }
  58. default:
  59. invariant(
  60. false,
  61. 'Unknown unit of work tag. This error is likely caused by a bug in ' +
  62. 'React. Please file an issue.',
  63. );
  64. }

completeWork 中有实质操作的是HostRoot、HostComponent、HostText、SuspenseComponent;ClassComponent、HostPortal、ContextProvider、IncompleteClassComponent只有pop操作,也就是为下一个HostComponent或HostText铺垫环境。HostText 对应的是Text,HostComponent 对应的是dom 节点,SuspenseComponent 对应的是懒加载组件。一个组件拆解到最后基本都是HostComponent、HostText。

HostText

如果 dom 是树,那么文本就是叶子。

  1. function completeWork(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. renderExpirationTime: ExpirationTime,
  5. ): Fiber | null {
  6. const newProps = workInProgress.pendingProps;
  7. switch (workInProgress.tag) {
  8. case HostText: {
  9. let newText = newProps;
  10. if (current && workInProgress.stateNode != null) {
  11. const oldText = current.memoizedProps;
  12. // 若文本更新,添加 side-effect
  13. updateHostText(current, workInProgress, oldText, newText);
  14. } else {
  15. if (typeof newText !== 'string') {
  16. // This can happen when we abort work.
  17. }
  18. const rootContainerInstance = getRootHostContainer();
  19. const currentHostContext = getHostContext();
  20. let wasHydrated = popHydrationState(workInProgress);
  21. if (wasHydrated) {
  22. if (prepareToHydrateHostTextInstance(workInProgress)) {
  23. markUpdate(workInProgress);
  24. }
  25. } else {
  26. workInProgress.stateNode = createTextInstance(newText, rootContainerInstance,
  27. currentHostContext, workInProgress);
  28. }
  29. }
  30. break;
  31. }
  32. }

在initial mount 时调用 ReactFiberHostConfig 中 createTextInstance来创建文本实例。

updateHostText

workInProgress.stateNode是记录当前fiber的节点,如果有值则调用 updateHostText 标记更新。updateHostTexty依据情况而调用。

  1. if(supportsMutation){
  2. updateHostText = function(current: Fiber,workInProgress: Fiber,
  3. oldText: string,newText: string,
  4. ) {
  5. // If the text differs, mark it as an update. All the work in done in commitWork.
  6. if (oldText !== newText) {
  7. markUpdate(workInProgress);
  8. }
  9. };
  10. }else if(supportsPersistence){
  11. updateHostText = function(current: Fiber,workInProgress: Fiber,
  12. oldText: string,
  13. newText: string
  14. ) {
  15. if (oldText !== newText) {
  16. // 如果文本内容不同,我们将为它创建一个新的文本实例
  17. const rootContainerInstance = getRootHostContainer();
  18. const currentHostContext = getHostContext();
  19. workInProgress.stateNode = createTextInstance(newText,rootContainerInstance,
  20. currentHostContext,workInProgress);
  21. markUpdate(workInProgress);
  22. }
  23. }
  24. }
  25. //标记更新
  26. function markUpdate(workInProgress: Fiber) {
  27. workInProgress.effectTag |= Update;
  28. }

若text值发生变化则调用 markUpdate 标记fiber。

HostComponent

HostComponet基本上是dom节点。

  1. function completeWork(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. renderExpirationTime: ExpirationTime,
  5. ): Fiber | null {
  6. const newProps = workInProgress.pendingProps;
  7. switch (workInProgress.tag) {
  8. case HostComponent: {
  9. popHostContext(workInProgress);
  10. const rootContainerInstance = getRootHostContainer();
  11. const type = workInProgress.type;
  12. if (current !== null && workInProgress.stateNode != null) {
  13. updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance);
  14. if (current.ref !== workInProgress.ref) { markRef(workInProgress); }
  15. } else {
  16. if (!newProps) {// This can happen when we abort work.
  17. break;
  18. }
  19. const currentHostContext = getHostContext();
  20. let wasHydrated = popHydrationState(workInProgress);
  21. if (wasHydrated) {
  22. if (prepareToHydrateHostInstance(workInProgress,rootContainerInstance,
  23. currentHostContext)
  24. ) {
  25. markUpdate(workInProgress);
  26. }
  27. } else {
  28. let instance = createInstance( type,newProps,rootContainerInstance,
  29. currentHostContext,workInProgress);
  30. appendAllChildren(instance, workInProgress, false, false);
  31. // 某些呈现程序需要初始挂载的提交时effect.没有 props.autoFocus 为true则标记更新。
  32. if (finalizeInitialChildren(instance,type,newProps,
  33. rootContainerInstance,currentHostContext)
  34. ) {
  35. markUpdate(workInProgress);
  36. }
  37. workInProgress.stateNode = instance;
  38. }
  39. if (workInProgress.ref !== null) {
  40. markRef(workInProgress);
  41. }
  42. }
  43. break;
  44. }
  45. }

在initial mount阶段会 createInstance、appendAllChildren、finalizeInitialChildren、markRef。createInstance检查并创建 dom 节点、appendAllChildren 追加所有字元素、finalizeInitialChildren 中设置dom的props。

绑定属性

修下App示例中的代码,如下所示:

  1. class App extends Component {
  2. render() {
  3. return <div id='appId' onClick={ ()=>{} } className='red'>Hello World!</div>;
  4. }
  5. }

id、className、onClick等只是div(HostComponent) 上的props。completeWork div时 createInstance会创建div实例、appendAllChildren会追加子元素,finalizeInitialChildren 会添加 props。
image.png

在setInitialDOMProperties中循环设置具体属性,shouldAutoFocusHostComponent中判断是否获取焦点。

  1. export function finalizeInitialChildren(domElement,type,props,
  2. rootContainerInstance,hostContext){
  3. setInitialProperties(domElement, type, props, rootContainerInstance);
  4. return shouldAutoFocusHostComponent(type, props);
  5. }
  6. export function setInitialProperties(domElement,tag,rawProps,rootContainerElement){
  7. const isCustomComponentTag = isCustomComponent(tag, rawProps);
  8. //...省略 react子遇到一dom元素时会绑定一些事件,例如为input元素绑定onChange事件。
  9. assertValidProps(tag, props);
  10. setInitialDOMProperties(tag,domElement,rootContainerElement,props,isCustomComponentTag);
  11. switch (tag) {
  12. // 省略...其它操作
  13. default:
  14. if (typeof props.onClick === 'function') {//绑定一个 function
  15. trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
  16. }
  17. break;
  18. }
  19. }
  20. // packages\react-dom\src\client\ReactDOMComponent.js
  21. function setInitialDOMProperties(tag,domElement, rootContainerElement,
  22. nextProps,isCustomComponentTag){
  23. for (const propKey in nextProps) {//循环添加属性
  24. if (!nextProps.hasOwnProperty(propKey)) {
  25. continue;
  26. }
  27. const nextProp = nextProps[propKey];
  28. if (propKey === STYLE) {//style
  29. setValueForStyles(domElement, nextProp);
  30. } else if (propKey === DANGEROUSLY_SET_INNER_HTML){//html
  31. const nextHtml = nextProp ? nextProp[HTML] : undefined;
  32. if (nextHtml != null) {
  33. setInnerHTML(domElement, nextHtml);
  34. }
  35. } else if (propKey === CHILDREN) {//children
  36. if (typeof nextProp === 'string') {
  37. const canSetTextContent = tag !== 'textarea' || nextProp !== '';
  38. if (canSetTextContent) {
  39. setTextContent(domElement, nextProp);
  40. }
  41. } else if (typeof nextProp === 'number') {
  42. setTextContent(domElement, '' + nextProp);
  43. }
  44. } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
  45. propKey === SUPPRESS_HYDRATION_WARNING
  46. ) {
  47. } else if (propKey === AUTOFOCUS) {
  48. } else if (registrationNameModules.hasOwnProperty(propKey)) {//绑定事件
  49. if (nextProp != null) {
  50. ensureListeningTo(rootContainerElement, propKey);//绑定事件
  51. }
  52. } else if (nextProp != null) {
  53. setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
  54. }
  55. }
  56. }

在setInitialDOMProperties中setValueForStyles、setInnerHTML、setTextContent、setValueForProperty、ensureListeningTo等,基本上包括了dom props上的各种情况。

updateHostComponent

  1. if (supportsMutation) {
  2. updateHostComponent = function(current: Fiber,workInProgress: Fiber,type: Type,
  3. newProps: Props,
  4. rootContainerInstance: Container
  5. ) {
  6. const oldProps = current.memoizedProps;
  7. if (oldProps === newProps) {return;}
  8. const instance: Instance = workInProgress.stateNode;
  9. const currentHostContext = getHostContext();
  10. const updatePayload = prepareUpdate(
  11. instance,type,oldProps,newProps,rootContainerInstance,currentHostContext
  12. );
  13. workInProgress.updateQueue = (updatePayload: any);
  14. if (updatePayload) {
  15. markUpdate(workInProgress);
  16. }
  17. };
  18. }else if(supportsPersistence){
  19. updateHostComponent = function(current,workInProgress,type,newProps,
  20. rootContainerInstance
  21. ) {
  22. const currentInstance = current.stateNode;
  23. const oldProps = current.memoizedProps;
  24. const childrenUnchanged = workInProgress.firstEffect === null;
  25. if (childrenUnchanged && oldProps === newProps) {
  26. workInProgress.stateNode = currentInstance;
  27. return;
  28. }
  29. const recyclableInstance: Instance = workInProgress.stateNode;
  30. const currentHostContext = getHostContext();
  31. let updatePayload = null;
  32. if (oldProps !== newProps) {
  33. updatePayload = prepareUpdate(
  34. recyclableInstance,type,oldProps,newProps,rootContainerInstance,currentHostContext);
  35. }
  36. if (childrenUnchanged && updatePayload === null) {
  37. workInProgress.stateNode = currentInstance;
  38. return;
  39. }
  40. let newInstance = cloneInstance(
  41. currentInstance,updatePayload,type,oldProps,newProps,
  42. workInProgress,childrenUnchanged,recyclableInstance,
  43. );
  44. if (
  45. finalizeInitialChildren(newInstance,type,newProps,rootContainerInstance,
  46. currentHostContext)
  47. ) {
  48. markUpdate(workInProgress);
  49. }
  50. workInProgress.stateNode = newInstance;
  51. if (childrenUnchanged) { markUpdate(workInProgress); }
  52. else { appendAllChildren(newInstance, workInProgress, false, false); }
  53. };
  54. }

HostRoot

在workLoop中,hostRoot是最先开始beginWork,是最后一个completeWork。hostRoot的completeWork标志着React Tree树的完成。

  1. function completeWork(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. renderExpirationTime: ExpirationTime,
  5. ): Fiber | null {
  6. const newProps = workInProgress.pendingProps;
  7. switch (workInProgress.tag) {
  8. case HostRoot: {
  9. popHostContainer(workInProgress);
  10. popTopLevelLegacyContextObject(workInProgress);
  11. const fiberRoot = (workInProgress.stateNode: FiberRoot);
  12. if (fiberRoot.pendingContext) {
  13. fiberRoot.context = fiberRoot.pendingContext;
  14. fiberRoot.pendingContext = null;
  15. }
  16. if (current === null || current.child === null) {
  17. popHydrationState(workInProgress);
  18. workInProgress.effectTag &= ~Placement;
  19. }
  20. updateHostContainer(workInProgress);
  21. break;
  22. }
  23. }

若没有effect链表,标志着没有更新,则不做处理。updateHostContainer 中的finalizeContainerChildren 和hostComponent中的 finalizeInitialChildren 不是同一个方法。更新再说。

  1. if(supportsMutation){
  2. updateHostContainer = function(workInProgress: Fiber) {
  3. // Noop
  4. };
  5. }else if (supportsPersistence){
  6. updateHostContainer = function(workInProgress: Fiber) {
  7. const portalOrRoot: {
  8. containerInfo: Container,
  9. pendingChildren: ChildSet,
  10. } = workInProgress.stateNode;
  11. const childrenUnchanged = workInProgress.firstEffect === null;
  12. if (childrenUnchanged) {
  13. // No changes, just reuse the existing instance.
  14. } else {
  15. const container = portalOrRoot.containerInfo;
  16. let newChildSet = createContainerChildSet(container);
  17. // 如果子元素发生了变化,我们必须将它们全部添加到集合中
  18. appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
  19. portalOrRoot.pendingChildren = newChildSet;
  20. // Schedule an update on the container to swap out the container.
  21. markUpdate(workInProgress);
  22. finalizeContainerChildren(container, newChildSet);
  23. }
  24. };
  25. }

Effect

在 completeWork 的同时,也会生成链表。使用链表可以更高效更新fiber。react Tree 是保存在内存中的dom tree数据,react tree上包含链表数据,react Tree数据会在commit阶段使用。链表请参考此处