React setState 源码解析
2021/12/26 17:09:49
本文主要是介绍React setState 源码解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1. setState是同步还是异步?
- 在legacy模式下,在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout等是同步的
- 在concurrent模式下,即使是在setTimeout中也是“异步”的
- 严格意义上来说,应该不是异步,只是执行时间比同步晚,这里直接用“异步”来讲
2. setState是如何实现异步批量更新的?
当 setState 方法被调用后,方法内部会创建一个包含过期时间和优先级lane的update(更新器),将最新的state挂载在update的 payload上,最后将此 update 存放到 fiber的 updateQueue队列中
简单点说,就是每个fiber身上都会有一个更新队列,在调用setState时,会将状态临时存储到更新器中,当需要更新组件时再来执行更新队列中的内容,清空更新队列
- 批量模式:如果是批量模式,则会将执行更新,清空更新队列的方法延迟调用。此时scheduleUpdateOnFiber 方法内只会调用 ensureRootIsScheduled ,在事件方法结束后,才会调用 flushSyncCallbackQueue 方法
- 非批量模式:非批量模式下,则会在 ensureRootIsScheduled 调用结束后直接执行更新方法(flushSyncCallbackQueue)
3. setState执行流程分析(伪代码)
- 首先,调用 setState 时,会调用 this.updater.enqueueSetState 方法
// 以下皆是伪代码 // 源码参考位置: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) } }
- getInstance 方法会获取 当前组件的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 }
4. 整体梳理
- 调用 setState时,会调用 enqueueSetState 方法创建一个包含 过期时间 和 优先级lane 以及最新state 的update更新器
- 调用 enqueueUpdate 方法将更新器存放到 fiber的更新队列中
- 调用 scheduleUpdateOnFiber 方法 schdule 更新
- 调用 ensureRootIsScheduled 方法创建一个 task 来 scheudle 更新
- 将更新任务 performWorkOnRoot 存放到 syncQueue 中等待调用
- 若是Legacy模式(非并发),不启用批量更新,直接调用更新方法 flushSyncCallbackQueue,执行更新,清空 syncQueue
- 若是Concurrent模式,则延迟调用 flushSyncCallbackQueue,等待所有同步任务执行结束后再调用 flushSyncCallbackQueue
- 在 performWorkOnRoot 方法中,会循环fiber身上的更新队列拿到最新的state,然后进行dom diff 以及 re-render
5. React.unstale_batchedUpdate
unstale_batchedUpdate方法可以在Legacy模式下,让 setState在 setTimeout中依旧是批量更新,其根本原理就是改变 excutionContext
export function batchedUpdates(fn){ let prevExecutionContext = excutionContext excutionContext |= batchedContext fn() excutionContext = prevExecutionContext }
6. 结语
以上是个人学习过程中的一些理解与总结,如果错误请指正
这篇关于React setState 源码解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16Vue3资料:新手入门必读教程
- 2024-11-16Vue3资料:新手入门全面指南
- 2024-11-16Vue资料:新手入门完全指南
- 2024-11-16Vue项目实战:新手入门指南
- 2024-11-16React Hooks之useEffect案例详解
- 2024-11-16useRef案例详解:React中的useRef使用教程
- 2024-11-16React Hooks之useState案例详解
- 2024-11-16Vue入门指南:从零开始搭建第一个Vue项目
- 2024-11-16Vue3学习:新手入门教程与实践指南
- 2024-11-16Vue3学习:从入门到初级实战教程