场景分析
/* @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
commitRootImpl被执行,会执行commitMutationEffects, commitLayoutEffects<br /> commitMutationEffects被执行,会执行commitPlacement<br /> commitPlacement 递归将所有的节点都加入到主节点<br /> commitLayoutEffects被执行,会执行commitLifeCycles<br /> commitLifeCycles 触发class组件都componentDidMount, componentDidUpdate
- discreteUpdates$1 (fn, a, b, c)
return runWithPriority$2(UserBlockingPriority$2, fn.bind(null, a, b, c));
18. discreteUpdates 方法,此方法调用下面方法 // 2
discreteUpdatesImpl(dispatchEvent, …) 实际是调用 discreteUpdates$1// ReactDOM在初始化时调用了下面方法
setBatchingImplementation(batchedUpdates$1, discreteUpdates$1, flushDiscreteUpdates, batchedEventUpdates$1); // 0
function setBatchingImplementation(_batchedUpdatesImpl, _discreteUpdatesImpl,
_flushDiscreteUpdatesImpl, _batchedEventUpdatesImpl) {
batchedUpdatesImpl = _batchedUpdatesImpl;
discreteUpdatesImpl = _discreteUpdatesImpl; // 1
flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
}
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才会状态合并,会进行回归)
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);
}
}
trapBubbledEvent
初始化时调用了 trapEventForPluginEventSystem
21. listenTo
调用了 trapBubbledEvent
22. ensureListeningTosetInitialDOMProperties
此方法对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里面的方法
在此结束
例子
渲染循环顺序
按顺序找到叶子节点,如果在找叶子节点的过程中碰到了text节点,就先渲染出来,找到叶子节点,从下往上渲染dom元素(如果子元素只有一个并且是text的节点,会只渲染一个节点,text不单独渲染)
import React from 'react';
import React from 'react-dom';
class Son extends React.Component {
state = {
name: "cc"
}
click = (e) => {
// 状态合并
e.stopPropagation();
this.setState({
name: 'Son1',
a: 1
})
this.setState({
name: 'Son2',
b: 2
})
this.setState({
name: 'Son3',
c: 3
})
}
render() {
return <div key={13} onClick={this.click}>this.state.name</div>
}
}
class Parent extends React.Component {
state = {
name: '10',
}
onClick: () => {
this._ref.setState({
name: 'Son1',
a: 1
})
this._ref.setState({
name: 'Son2',
b: 2
})
this._ref.setState({
name: 'Son3',
c: 3
})
this.setState({
name: 'Parent',
})
}
render() {
return <div onClick={click} ref: function (_ref) { this._ref = _ref; } key={9}>
{this.state.name}
<Son key={2} />
</div>
}
}
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;
}
}