We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fiber 的 updateQueue 属性在不同类型的 fiber 节点中含义不同,本节主要介绍HostRoot、HostComponent、ClassComponent、FunctionComponent这几种类型的 fiber updateQueue 的作用。
HostRoot
HostComponent
ClassComponent
FunctionComponent
updateQueue
ReactDOM.render/ReactDOM.hydrate
this.setState
useEffect
useLayoutEffect
下面我会从render阶段和commit 阶段介绍对updateQueue的处理。
render阶段和
render 阶段主要是为 updateQueue 赋值,并计算 updateQueue。commit 阶段遍历 updateQueue 执行相应的操作
HostRootFiber就是root容器对应的 fiber 节点。
HostRootFiber
root
对于HostRootFiber,updateQueue用于存储ReactDOM.render或者ReactDOM.hydrate的第三个参数(回调函数)。
ReactDOM.render
ReactDOM.hydrate
ReactDOM.render(<Home />, document.getElementById("root"), () => { console.log("render 回调...."); }); // 或者 ReactDOM.hydrate(<Home />, document.getElementById("root"), () => { console.log("hydrate 回调...."); });
HostRootFiber的updateQueue是一个环状链表。
初次渲染时,updateContainer方法会为HostRootFiber添加一个update对象,如下:
updateContainer
update
var update = { eventTime: eventTime, lane: lane, tag: UpdateState, payload: null, callback: null, // ReactDOM.render或者ReactDOM.hydrate方法的第三个参数 next: null, }; update.next = update; // 环状链表 shared.pending指向最后一个更新的对象 HostRootFiber.updateQueue = { baseState: null, effects: null, firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: update, }, };
beginWork 阶段调用processUpdateQueue方法遍历updateQueue.shared.pending中的更新队列,计算 state。如果更新的对象update的callback有值,则将update存入updateQueue.effects数组中。同时将当前 fiber 标记为具有Callback的副作用
processUpdateQueue
updateQueue.shared.pending
callback
updateQueue.effects
Callback
HostRootFiber.updateQueue = { baseState: { element }, effects: [ { callback: ƒ(), payload: { element }, next: null, }, ], shared: { pending: null, }, };
在commitLayoutEffects阶段调用commitLifeCycles方法,commitLifeCycles方法调用commitUpdateQueue执行回调方法。
commitLayoutEffects
commitLifeCycles
commitUpdateQueue
function commitUpdateQueue(finishedWork, finishedQueue, instance) { // 遍历effects,执行callback回调 var effects = finishedQueue.effects; finishedQueue.effects = null; // effects重置为null if (effects !== null) { for (var i = 0; i < effects.length; i++) { var effect = effects[i]; var callback = effect.callback; if (callback !== null) { effect.callback = null; callback(instance); } } } }
对于类组件,updateQueue 存的是更新队列,即 this.setState 的更新对象链表,是一个环状链表。
this.setState实际上会调用this.enqueueSetState方法构造一个更新对象,并添加到队列中。shared.pending指向最后一个更新。
this.enqueueSetState
shared.pending
// 更新对象 var update = { callback: null, // this.setState的第二个参数,即回调函数 eventTime, lane: 1, next: null, payload: { count: 4 }, // this.setState的第一个参数 tag: 0, }; update.next = update; // 环状链表 fiber.updateQueue = { baseState: { count: 1 }, effects: null, shared: { pending: update, }, };
beginWork 阶段,updateClassInstance调用processUpdateQueue遍历更新的队列,计算 state,最终处理后的updateQueue如下:
updateClassInstance
fiber.updateQueue = { baseState: { count: 2 }, effects: [ { callback, // callback存的是this.setState的第二个参数,即回调函数 next: null, payload: { count: 2 }, tag: 0, }, ], firstBaseUpdate: null, lastBaseUpdate: null, shared: { pending: null }, };
commitLayoutEffects阶段调用commitUpdateQueue判断如果updateQueue不为 null,则调用commitUpdateQueue遍历updateQueue.effects,执行setState的回调
setState
HostComponent fiber,就是原生的 div、span 等 HTML 标签
HostComponent fiber 的updateQueue在初次渲染时为 null。只有在更新阶段,dom 的属性发生了变更,才不为 null。
HostComponent 的 updateQueue 存的是需要更新的属性键值对,此时 updateQueue 就是一个数组。如果 dom 上的属性没有发生变化,但是事件监听函数引用发生了变化,则 updateQueue 为空数组
completeUnitOfWork阶段调用 updateHostComponent对比新旧 props 的变化。
completeUnitOfWork
updateHostComponent
function updateHostComponent() { var updatePayload = prepareUpdate(instance, type, oldProps, newProps); workInProgress.updateQueue = updatePayload; if (updatePayload) { markUpdate(workInProgress); } } function prepareUpdate() { return diffProperties(domElement, type, oldProps, newProps); }
updateHostComponent主要逻辑如下:
prepareUpdate
updatePayload
diffProperties会比较 oldProps 和 newProps,找出有差异的属性,比如:
diffProperties
oldProps
newProps
// 更新前 <div id="1" test1="2">1</div> // 更新后 <div id="2" test1="3" test2="4">2</div>
旧的 id = 1,而新的id = 2,则id发生了变更,因此需要添加到updatePayload中,此时updatePayload = ['id', 2]。
id = 1
id = 2
id
updatePayload = ['id', 2]
这里需要注意,如果我们的属性没有变更,但是 onClick 等监听函数的引用发生了变更,则diffProperties会返回一个空数组以标记该节点需要更新
// 更新前 <div onClick={() => { console.log('onclick')}}>1</div> // 更新后 <div onClick={() => { console.log('onclick')}}>1</div>
虽然 div 节点更新前后属性没有发生变化,但是 onClick 的引用发生了变化,则 updatePayload = []
div
onClick
updatePayload = []
commitMutationEffects 阶段,如果updateQueue不为 null,则调用commitUpdate更新 dom 属性
commitUpdate
函数组件的updateQueue 存的是 useEffect 以及 useLayoutEffect 的监听函数,并且是一个环状链表。lastEffect指向最后一个effect。函数组件每次执行时,都会重新初始化 updateQueue
lastEffect
effect
renderWithHooks函数在执行函数组件时,构造 effect 对象,并添加到updateQueue队列中
renderWithHooks
// effect对象 var effect = { tag: tag, // useLayoutEffect的tag等于3。useEffect的tag等于5 create: create, // useEffect或者useLayoutEffect的第一个参数,即监听函数, destroy: destroy, // useEffect或者useLayoutEffect的清除函数 deps: deps, // 依赖。即useEffect或者useLayoutEffect的第二个参数,即依赖 // Circular next: null, }; effect.next = effect; fiber.updateQueue = { lastEffect: effect, };
commitMutationEffects
commitHookEffectListUnmount
commitHookEffectListMount
schedulePassiveEffects
// 执行useLayoutEffect的清除函数 function commitHookEffectListUnmount(tag, finishedWork) { var updateQueue = finishedWork.updateQueue; var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { var firstEffect = lastEffect.next; var effect = firstEffect; do { // useLayoutEffect if ((effect.tag & 3) === 3) { var destroy = effect.destroy; // 清除函数 effect.destroy = undefined; if (destroy !== undefined) { destroy(); } } effect = effect.next; } while (effect !== firstEffect); } } // 执行useLayoutEffect的监听函数 function commitHookEffectListMount(tag, finishedWork) { var updateQueue = finishedWork.updateQueue; var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { var firstEffect = lastEffect.next; var effect = firstEffect; do { if ((effect.tag & 3) === 3) { // useLayoutEffect的监听函数 var create = effect.create; effect.destroy = create(); } effect = effect.next; } while (effect !== firstEffect); } }
// 调度useEffect。将useEffect的监听函数以及清除函数放入微任务队列等待异步执行 function schedulePassiveEffects(finishedWork) { var updateQueue = finishedWork.updateQueue; var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { var firstEffect = lastEffect.next; var effect = firstEffect; do { const { next, tag } = effect; if ((tag & 5) === 5) { enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); // 将useEffect的清除函数放入微任务队列 enqueuePendingPassiveHookEffectMount(finishedWork, effect); // 将useEffect的监听函数放入微任务队列 } effect = next; } while (effect !== firstEffect); } }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
概述
updateQueue
存的是ReactDOM.render/ReactDOM.hydrate
的第三个回调参数,是个环状链表updateQueue
存的是this.setState
的更新队列,是个环状链表updateQueue
存的是useEffect
或者useLayoutEffect
的监听函数,是个环状链表updateQueue
存的是在更新期间有变更的属性的键值对,是个数组下面我会从
render阶段和
commit 阶段介绍对updateQueue
的处理。render 阶段主要是为 updateQueue 赋值,并计算 updateQueue。commit 阶段遍历 updateQueue 执行相应的操作
HostRootFiber 容器节点
HostRootFiber
就是root
容器对应的 fiber 节点。对于
HostRootFiber
,updateQueue
用于存储ReactDOM.render
或者ReactDOM.hydrate
的第三个参数(回调函数)。HostRootFiber
的updateQueue
是一个环状链表。初次渲染时,
updateContainer
方法会为HostRootFiber
添加一个update
对象,如下:render 阶段
beginWork 阶段调用
processUpdateQueue
方法遍历updateQueue.shared.pending
中的更新队列,计算 state。如果更新的对象update
的callback
有值,则将update
存入updateQueue.effects
数组中。同时将当前 fiber 标记为具有Callback
的副作用commit 阶段
在
commitLayoutEffects
阶段调用commitLifeCycles
方法,commitLifeCycles
方法调用commitUpdateQueue
执行回调方法。类组件
对于类组件,updateQueue 存的是更新队列,即 this.setState 的更新对象链表,是一个环状链表。
this.setState
实际上会调用this.enqueueSetState
方法构造一个更新对象,并添加到队列中。shared.pending
指向最后一个更新。render 阶段
beginWork 阶段,
updateClassInstance
调用processUpdateQueue
遍历更新的队列,计算 state,最终处理后的updateQueue
如下:commit 阶段
commitLayoutEffects
阶段调用commitUpdateQueue
判断如果updateQueue
不为 null,则调用commitUpdateQueue
遍历updateQueue.effects
,执行setState
的回调HostComponent
HostComponent
fiber,就是原生的 div、span 等 HTML 标签HostComponent
fiber 的updateQueue
在初次渲染时为 null。只有在更新阶段,dom 的属性发生了变更,才不为 null。HostComponent
的 updateQueue 存的是需要更新的属性键值对,此时 updateQueue 就是一个数组。如果 dom 上的属性没有发生变化,但是事件监听函数引用发生了变化,则 updateQueue 为空数组beginWork
completeUnitOfWork
阶段调用updateHostComponent
对比新旧 props 的变化。updateHostComponent
主要逻辑如下:prepareUpdate
比较属性,找出有差异的属性键值对存储在updatePayload
中updatePayload
不为 null,则将当前 fiber 标记为具有更新的副作用diffProperties
会比较oldProps
和newProps
,找出有差异的属性,比如:旧的
id = 1
,而新的id = 2
,则id
发生了变更,因此需要添加到updatePayload
中,此时updatePayload = ['id', 2]
。这里需要注意,如果我们的属性没有变更,但是 onClick 等监听函数的引用发生了变更,则
diffProperties
会返回一个空数组以标记该节点需要更新虽然
div
节点更新前后属性没有发生变化,但是onClick
的引用发生了变化,则updatePayload = []
commit 阶段
commitMutationEffects 阶段,如果
updateQueue
不为 null,则调用commitUpdate
更新 dom 属性FunctionComponent 函数组件
函数组件的
updateQueue
存的是useEffect
以及useLayoutEffect
的监听函数,并且是一个环状链表。lastEffect
指向最后一个effect
。函数组件每次执行时,都会重新初始化updateQueue
beginWork 阶段
renderWithHooks
函数在执行函数组件时,构造 effect 对象,并添加到updateQueue
队列中commit 阶段
commitMutationEffects
阶段调用commitHookEffectListUnmount
执行useLayoutEffect
的清除函数commitLayoutEffects
阶段调用commitHookEffectListMount
执行useLayoutEffect
的监听函数。然后调用schedulePassiveEffects
将useEffect
的监听函数和清除函数放入微任务队列执行,useEffect
是异步执行的The text was updated successfully, but these errors were encountered: