当 setState 方法被调用后,方法内部会创建一个包含过期时间和优先级lane的update(更新器),将最新的state挂载在update的 payload上,最后将此 update 存放到 fiber的 updateQueue队列中
简单点说,就是每个fiber身上都会有一个更新队列,在调用setState时,会将状态临时存储到更新器中,当需要更新组件时再来执行更新队列中的内容,清空更新队列
// 以下皆是伪代码 // 源码参考位置:ReactBaseClasses: react/src/ReactBaseClasses.js export class Component{ constructure(){ this.updater = ClassComponentUpdater } setState(partialState,callback){ this.updater.enqueueSetState(this,partialState,callback,'setState') } } // 源码参考位置:ReactFiberClassComponent: react-recondiler/src/ReactFiberClassComponent.js let ClassComponentUpdater = { enqueueSetState(inst,payload,callback){ const fiber = getInstance(inst) const eventTime = requestEventTime() // 源码参考位置: ReactFiberLane: react-reconciler/src/ReactFiberLane.js const lane = requestUpdateLane(fiber) const update = createUpdate(eventTime,lane) update.payload = payload callback && (update.callback = callback) // Add the update to fiber's updateQueue enqueueUpdate(fiber,update) // schedule update scheduleUpdateOnFiber(fiber) } }
function getInstance(inst){ return inst._reactInternal }
requestEventTime 方法用来计算事件优先级
createUpdate会创建一个包含过期时间和优先级lane的update(更新器),然后将最新的state挂载在update的 payload上
enqueueUpdate 会将此更新器存放到当前fiber的更新队列中
// updateQueue 在react源码中是一个循环链表,此处用数组模拟 function enqueueUpdate(fiber,update){ fiber.updateQueue.push(update) }
2. enqueueSetState 在创建更新之后,会调用 scheduleUpdateOnFiber 来 schedule 更新
function scheduleUpdateOnFiber(fiber){ const root = markUpdateLaneFormFiberToRoot(fiber) if(root === null){ return null } // create a task to update from root ensureRootIsScheduled(root) // NoContext 和 NoMode 的情况,即不是 concurrent 模式 if(excutionContext === NoContext && (fiber.mode && ConcurrentMode) === NoMode){ // 非并发模式下,不启用批量更新,直接调用更新方法 flushSyncCallbackQueue() } } // Note:react源码中用位操作和进制表示 Context 和 Mode 等 // 参考文件:mode: react-reconciler/src/ReactTypeOfMode.js // 参考文件:Lane : react-reconciler/src/ReactFiberLane.new.js // 参考文件:Context: react-reconciler/src/ReactFiberWorkLoop.new.js
markUpdateLaneFormFiberToRoot 用来获取根结点,从根结点出发schedule更新
3. 调用 ensureRootIsScheduled 开始实施更新
function ensureRootIsScheduled(rootFiber){ // reconciler root let nextLanes = SyncLane // 1 let nextCallbackPriority = SyncLanePriority // 12 // is working fiber's priority let existingCallbackPriority = rootFiber.callbackPriority // 如果这个新的更新和当前根结点已调度的更新相等,那就直接返回,复用上次的更新,不再创建新的更新任务 // 并发模式下即使使用 setTimeout 也无法打断批量更新的原因就是在于这里 if(existingCallbackPriority === nextCallbackPriority){ return } scheduleSyncCallback(performWorkOnRoot.bind(null,rootFiber)) // put in micro task // 用微任务来 模拟 延迟flushSyncCallbackQueue queueMicrotask(flushSyncCallbackQueue) rootFiber.callbackPriority = nextCallbackPriority }
4. 调用 scheduleSyncCallback 将更新函数存放到 syncQueue 队列中等待更新
// put performWorkOnRoot to a queue, waiting for excuting function scheduleSyncCallback(cb){ syncQueue.push(cb) }
5. 在 performWorkOnRoot方法中 实施更新,从根结点依次向下更新
// render (dom diff / render) function performWorkOnRoot(workInProcess){ let root = workInProcess // start reconciler while(workInProcess){ if(workInProcess.tag === ClassComponent){ let inst = workInProcess.stateNode // get instance // get the newState inst.state = processUpdateQueue(inst,workInProcess) // re-render to gain the new Virtual Dom,then diff inst.render() } // update child workInProcess = workInProcess.child } commitRoot(root) }
commitRoot 方法中会重置 callbackPriority
6. 在 processUpdateQueue 中获取最新的 state
function processUpdateQueue(inst,fiber){ return fiber.updateQueue.reduce((state,action)=>{ if(typeof update.payload === 'function'){ // type protect update.payload = update.payload(state) } return { ...state, ...update.payload } },inst.state) }
7. 利用flushSyncCallbackQueue 清空 syncQueue 更新队列,执行更新
// syncQueue 中存放的就是更新操作,此时一一执行,释放更新队列 function flushSyncCallbackQueue(){ syncQueue.forEach(cb=>cb()) syncQueue.length = 0 }
unstale_batchedUpdate方法可以在Legacy模式下,让 setState在 setTimeout中依旧是批量更新,其根本原理就是改变 excutionContext
export function batchedUpdates(fn){ let prevExecutionContext = excutionContext excutionContext |= batchedContext fn() excutionContext = prevExecutionContext }
以上是个人学习过程中的一些理解与总结,如果错误请指正