场景分析

/* @license React v16.9.0 /
typescript:
(fiber as FiberNode).stateNode // 当前状态
(fiber as FiberNode).updateQueue // 队列 state queue

情况1,在一个方法体内执行3次setState, 状态如何合并,nextState,prevState呈现什么形态
state = { // 默认state
name: ‘CC’
}

onClick: (e) => {
e.stopPropagation();
debugger;
self.setState({
name: ‘Son1’,
a: 1
})
self.setState({
name: ‘Son2’,
b: 2
})
self.setState({
name: ‘Son3’,
c: 3
})
}

(fiber as FiberNode).stateNode = {
“name”: “CC”
}
(fiber as FiberNode).updateQueue = {
“baseState”: {
“name”: “CC”
},
“firstUpdate”: {
“expirationTime”: 1073741823,
“suspenseConfig”: null,
“tag”: 0,
“payload”: {
“name”: “Son1”,
“a”: 1
},
“callback”: null,
“next”: {
“expirationTime”: 1073741823,
“suspenseConfig”: null,
“tag”: 0,
“payload”: {
“name”: “Son2”,
“b”: 2
},
“callback”: null,
“next”: {
“expirationTime”: 1073741823,
“suspenseConfig”: null,
“tag”: 0,
“payload”: {
“name”: “Son3”,
“c”: 3
},
“callback”: null,
“next”: null,
“nextEffect”: null,
“priority”: 98
},
“nextEffect”: null,
“priority”: 98
},
“nextEffect”: null,
“priority”: 98
},
“lastUpdate”: {
“expirationTime”: 1073741823,
“suspenseConfig”: null,
“tag”: 0,
“payload”: {
“name”: “Son3”,
“c”: 3
},
“callback”: null,
“next”: null,
“nextEffect”: null,
“priority”: 98
},
“firstCapturedUpdate”: null,
“lastCapturedUpdate”: null,
“firstEffect”: null,
“lastEffect”: null,
“firstCapturedEffect”: null,
“lastCapturedEffect”: null
}

onClick 会被包装一层变成 function noop {} // 为了实现proxy的功能,在方法执行前后干些什么

第二阶段 触发事件

0.setState
1. invokeGuardedCallbackDev 里面有一个内部函数callCallback会被触发
invokeGuardedCallbackImpl = invokeGuardedCallbackDev; // 别名
var invokeGuardedCallbackImpl$1 = invokeGuardedCallbackImpl; // 别名2
2. invokeGuardedCallback
3. invokeGuardedCallbackAndCatchFirstError
4. executeDispatch
5. executeDispatchesInOrder
6. executeDispatchesAndRelease
7. executeDispatchesAndReleaseTopLevel
8. forEachAccumulated
9. runEventsInBatch
10. runExtractedPluginEventsInBatch
11. handleTopLevel
12. batchedEventUpdates
13. dispatchEventForPluginEventSystem
14. dispatchEvent
15. unstable_runWithPriority (priorityLevel, eventHandler)
别名 Scheduler_runWithPriority
return eventHandler();
16. runWithPriority$2(reactPriorityLevel, fn)
处理二
renderRoot 方法调用 commitRoot.bind(…)
commitRoot 被执行触发 runWithPriority$2
runWithPriority$2 传入了 fn = commitRootImpl.bind(null, root, renderPriorityLevel)
runWithPriority$2 方法 return Scheduler_runWithPriority(priorityLevel, fn);
Scheduler_runWithPriority 方法执行了 fn

  1. commitRootImpl被执行,会执行commitMutationEffects, commitLayoutEffects<br /> commitMutationEffects被执行,会执行commitPlacement<br /> commitPlacement 递归将所有的节点都加入到主节点<br /> commitLayoutEffects被执行,会执行commitLifeCycles<br /> commitLifeCycles 触发class组件都componentDidMount, componentDidUpdate
  1. discreteUpdates$1 (fn, a, b, c)
    return runWithPriority$2(UserBlockingPriority$2, fn.bind(null, a, b, c));
    18. discreteUpdates 方法,此方法调用下面方法 // 2
    discreteUpdatesImpl(dispatchEvent, …) 实际是调用 discreteUpdates$1
    1. // ReactDOM在初始化时调用了下面方法
    2. setBatchingImplementation(batchedUpdates$1, discreteUpdates$1, flushDiscreteUpdates, batchedEventUpdates$1); // 0
    3. function setBatchingImplementation(_batchedUpdatesImpl, _discreteUpdatesImpl,
    4. _flushDiscreteUpdatesImpl, _batchedEventUpdatesImpl) {
    5. batchedUpdatesImpl = _batchedUpdatesImpl;
    6. discreteUpdatesImpl = _discreteUpdatesImpl; // 1
    7. flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
    8. batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
    9. }

    19. dispatchDiscreteEvent (topLevelType, eventSystemFlags, nativeEvent)
    调用discreteUpdates(dispatchEvent, …)方法

第一阶段 绑定事件 ,事件代理

情景一
1.绑定 给 document 对象加入click的DOM2级事件,将dispatchEvent.bind绑定到事件中
2.触发 dispatchEvent方法
触发时,拿到e.target, 从e.target中将FiberNode取出来
每个真实DOM都对应一个FiberNode
从FiberNode中把onClick方法拿出来,
继续向下执行,传递回调函数,利用js模拟事件,触发一个事件,执行onClick函数,此时这个模拟事件相当于一个proxy
3.onClick执行完毕,虚拟DOM此时因为OnClick已经计算完毕
回调函数回归,触发commitRoot.bind(FiberNodeRoot),触发commitRootImpl.bind(FiberNodeRoot)
4.执行虚拟DOM变更
(只有在React事件绑定中,setState才会状态合并,会进行回归)

  1. trapEventForPluginEventSystem 重要
    element = docunent, rawEventName = ‘click’, listener = function
    addEventBubbleListener(element, rawEventName, listener);
    function addEventBubbleListener(element, eventType, listener) {
    element.addEventListener(eventType, listener, false);
    }

    function trapEventForPluginEventSystem(element, topLevelType, capture) {
    var listener = void 0;
    switch (getEventPriority(topLevelType)) {
    case DiscreteEvent:
    listener = dispatchDiscreteEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
    break;
    case UserBlockingEvent:
    listener = dispatchUserBlockingUpdate.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
    break;
    case ContinuousEvent:
    default:
    listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
    break;
    }

    var rawEventName = getRawEventName(topLevelType);
    if (capture) {
    addEventCaptureListener(element, rawEventName, listener);
    } else {
    addEventBubbleListener(element, rawEventName, listener);
    }
    }

  1. trapBubbledEvent
    初始化时调用了 trapEventForPluginEventSystem
    21. listenTo
    调用了 trapBubbledEvent
    22. ensureListeningTo

  2. setInitialDOMProperties
    此方法对props进行了处理
    会对所有的定义的React事件进行处理
    registrationNameModules = [onClick, …]
    else if (registrationNameModules.hasOwnProperty(propKey)) {
    if (nextProp != null) {
    if (true && typeof nextProp !== ‘function’) {
    warnForInvalidEventListener(propKey, nextProp);
    }
    ensureListeningTo(rootContainerElement, propKey);
    }
    }
    24. setInitialProperties
    初始化属性
    // 情景介绍一,假设你的标签不是一个input,textarea,option,select,会执行下面代码
    if (typeof props.onClick === ‘function’) {
    trapClickOnNonInteractiveElement(domElement);
    }
    function trapClickOnNonInteractiveElement(node) {
    node.onclick = noop;
    }
    25. finalizeInitialChildren
    26. completeWork
    初始化React虚拟DOM

第三阶段 事件回收

commitRoot(root)
runRootCallback(root, callback, isSync)
flushSyncCallbackQueueImpl()
unstable_runWithPriority(priorityLevel, eventHandler) React里面的方法 重要
runWithPriority$2(reactPriorityLevel, fn)
flushSyncCallbackQueue()
discreteUpdates$1
discreteUpdates
dispatchDiscreteEvent
requestHostCallback React里面的方法
requestAnimationFrame
performWorkUntilDeadline React里面的方法
requestAnimationFrame React里面的方法
在此结束

例子

渲染循环顺序

  1. 按顺序找到叶子节点,如果在找叶子节点的过程中碰到了text节点,就先渲染出来,找到叶子节点,从下往上渲染dom元素(如果子元素只有一个并且是text的节点,会只渲染一个节点,text不单独渲染)
  1. import React from 'react';
  2. import React from 'react-dom';
  3. class Son extends React.Component {
  4. state = {
  5. name: "cc"
  6. }
  7. click = (e) => {
  8. // 状态合并
  9. e.stopPropagation();
  10. this.setState({
  11. name: 'Son1',
  12. a: 1
  13. })
  14. this.setState({
  15. name: 'Son2',
  16. b: 2
  17. })
  18. this.setState({
  19. name: 'Son3',
  20. c: 3
  21. })
  22. }
  23. render() {
  24. return <div key={13} onClick={this.click}>this.state.name</div>
  25. }
  26. }
  27. class Parent extends React.Component {
  28. state = {
  29. name: '10',
  30. }
  31. onClick: () => {
  32. this._ref.setState({
  33. name: 'Son1',
  34. a: 1
  35. })
  36. this._ref.setState({
  37. name: 'Son2',
  38. b: 2
  39. })
  40. this._ref.setState({
  41. name: 'Son3',
  42. c: 3
  43. })
  44. this.setState({
  45. name: 'Parent',
  46. })
  47. }
  48. render() {
  49. return <div onClick={click} ref: function (_ref) { this._ref = _ref; } key={9}>
  50. {this.state.name}
  51. <Son key={2} />
  52. </div>
  53. }
  54. }
  55. ReactDOM.render(<Parent key={1} />, document.getElementById('root'));

首次渲染
10
cc
点击子元素cc,进入completeWork被执行
第一次FiberNode key = null ,
stateNode: text text.nodeValue = “10”, sibling key = 2 tag = 1, return key=9 tag=6
第二次FiberNode key = 13, elementType = div , tag = 5
第三次FiberNode key = 2,tag = 1,注意此时stateNode状态已被更新
第四次FiberNode key = 9,tag=3
第五次FiberNode key = 1,tag=1
第六次FiberNode key=null tag=3,child key=1
回归流程进入commitRoot(root)
root.current = 第六次的FiberNode
root.current.child = 第五次的FiberNode
root.current.child.child = 第四次的FiberNode
root.current.child.child.child = 第一次的FiberNode
root.current.child.child.child.sibling = 第三次的FiberNode
root.current.child.child.child.sibling.child = 第二次的FiberNode

我们注意看key=13的这个 ,此时真实DOM还没有被更新 stateNode: div,注意他的tag是5,他是一个string React.createElement(‘div’, …)类型的组件,没有state(忽略hooks的情况),我们向上看他的父组件root.current.child.child.child.sibling.child.return (key=2) stateNode: Mod,此时state状态已更新

root.current.child.child.child.sibling.child.return.stateNode
stateNode: Mod
{
  componentDidCatch: ƒ (error, info)
  context: {}
  props: {}
  refs: {}
  state: {name: "Son3", a: 1, b: 2, c: 3}
  updater: {isMounted: ƒ, enqueueSetState: ƒ, enqueueReplaceState: ƒ, enqueueForceUpdate: ƒ}
  _reactInternalFiber: FiberNode {tag: 1, key: "2", stateNode: Mod, elementType: ƒ, type: ƒ, …}
  _reactInternalInstance: {_processChildContext: ƒ}
  isMounted: (...)
  replaceState: (...)
}

进入 commitRootImpl方法 nextEffect被赋值,并且执行commitMutationEffects方法。
进入 commitMutationEffects 方法
nextEffect 全局属性 nextEffect:FiberNode格式,
需要注意FiberNode.nextEffect
[
nextEffect: tag: 6, key: null,
nextEffect.nextEffect : tag: 5, key: “13”,
nextEffect.nextEffect.nextEffect: tag: 1, key: “2”,
nextEffect.nextEffect.nextEffect.nextEffect: tag: 5, key: “9”,
nextEffect.nextEffect.nextEffect.nextEffect.nextEffect: tag: 1, key: “1”,
nextEffect.nextEffect.nextEffect.nextEffect.nextEffect: null
]

nextEffect.effectTag = 4
var primaryEffectTag = 4 & (2 | 4 | 8);
// 当前DIFF类型 默认0 ,4 update,8 Deletion,2 Placement,6 PlacementAndUpdate

进入 update 的 switch case
commitWork —> commitUpdate 更新正式DOM

function commitMutationEffects(renderPriorityLevel) {
  // TODO: Should probably move the bulk of this function to commitWork.
  while (nextEffect !== null) {
    setCurrentFiber(nextEffect);

    var effectTag = nextEffect.effectTag;

    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    if (effectTag & Ref) {
      var current$$1 = nextEffect.alternate;
      if (current$$1 !== null) {
        commitDetachRef(current$$1);
      }
    }

    // The following switch statement is only concerned about placement,
    // updates, and deletions. To avoid needing to add a case for every possible
    // bitmap value, we remove the secondary effects from the effect tag and
    // switch on that value.
    var primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement:
        {
          commitPlacement(nextEffect);
          // Clear the "placement" from effect tag so that we know that this is
          // inserted, before any life-cycles like componentDidMount gets called.
          // TODO: findDOMNode doesn't rely on this any more but isMounted does
          // and isMounted is deprecated anyway so we should be able to kill this.
          nextEffect.effectTag &= ~Placement;
          break;
        }
      case PlacementAndUpdate:
        {
          // Placement
          commitPlacement(nextEffect);
          // Clear the "placement" from effect tag so that we know that this is
          // inserted, before any life-cycles like componentDidMount gets called.
          nextEffect.effectTag &= ~Placement;

          // Update
          var _current = nextEffect.alternate;
          commitWork(_current, nextEffect);
          break;
        }
      case Update:
        {
          var _current2 = nextEffect.alternate;
          commitWork(_current2, nextEffect);
          break;
        }
      case Deletion:
        {
          commitDeletion(nextEffect, renderPriorityLevel);
          break;
        }
    }

    // TODO: Only record a mutation effect if primaryEffectTag is non-zero.
    recordEffect();

    resetCurrentFiber();
    nextEffect = nextEffect.nextEffect;
  }
}