使用react也有将近一年了,在使用的过程中,我相信你也会像我一样,存在很多疑惑的点; 举些例子,比如为什么react有些生命钩子会执行多次,而有些只会安全的执行一次?react 16 大版本更新的fiber到底是个什么东西?诸如此类的问题,我也百思不得其解;所以我踏上了探究源码之路;
总所周知,react源码不是一般的多,直接阅读react源码,真的是劝退... 在搜集react源码资料的时候,发现比较全和新的资料也很少,偶然一次机会看到奇舞团大佬按照react源码思路自己debug造了一个;在学习他的源码时候,我也私下和他交流了很多,真的是听君一席话,胜读十年书呀;2333....(还是自己太菜了,非常感谢大佬解答我的问题)
言归正传,这里强烈推荐他电子书,源码系列文章是基于最新的16.13.1解析的; 虽然没有更完,但是写得相当精彩,反正我是看了还想看那种。(有点崔更了,哈哈)
本文是根据最新的16.13.1进行解析,目的是把整体的源码流程看懂个大概,并不会深入到很细节的东西; 也就是说把react的整体更新流程弄明白,可以帮助你更好的去探究最终的源码细节,如果有不正确的地方,还望大佬们指正; 虽说现在更新到了16.13.1版本了,但是整体的架构依然没有变,这里我推荐几个必读的资料,很精彩;
在了解react架构之前,我们还需要了解一下浏览器渲染原理,主流的浏览器刷新频率为60Hz,即每(1000ms / 60Hz)16.6ms浏览器刷新一次。我们知道,JS是可以操作DOM的,所以JS脚本执行和浏览器布局、绘制是处于同一线程(渲染线程)。 也就是浏览器在一帧的时间内要完成以下工作
在16大版本之前,也就是React15架构只分为两层,Reconciler(协调器,可不中断)+ Renderer(渲染器,不可中断);也就是说协调阶段,同步(递归更新完)更新的;这很容易造成JS执行时间过长,超出了16.6ms,也就是说一旦开始更新,就不可中断,一口气做完。会造成卡顿,这样的用户体验非常差;
react团队发现,让用户操作感觉不到卡顿,操作以外的有延迟,卡顿一下,用户是完全可以接受的;JS执行时间过长,所以react更改了架构;React16架构可以分为三层:
要理解react的更新流程,我觉得最好的方式是画流程图,结合一点源码注释;不然在学习源码的过程会非常的混乱;先看react的初始化阶段。
// ReactDOM.render(<App name="Hello"/>, document.querySelector('#app')); const ReactDOM = { render(element, container) { // 创建 FiberRoot const root = container._reactRootContainer = new ReactRoot(container); // 首次渲染不需要批量更新 DOMRenderer.unbatchedUpdates(() => { //调用 FiberRoot 的render方法开始渲染 root.render(element); }) } 复制代码
export default class ReactRoot { constructor(container) { // RootFiber tag === 3 this.current = new FiberNode(3, null, null); // 初始化rootFiber的updateQueue initializeUpdateQueue(this.current); // RootFiber指向FiberRoot this.current.stateNode = this; // 应用挂载的根DOM节点 this.containerInfo = container; // root下已经render完毕的fiber this.finishedWork = null; } } 复制代码
batchedUpdates
, 会输出1,2,3batchedUpdates
,相当于使用定时器的效果,会输出0,0,0batchedUpdates
handleClick = () => { // 主动`unbatchedUpdates` // setTimeout(() => { // this.countNumber() // }, 0) // setTimeout中没有`batchedUpdates` setTimeout(() => { batchedUpdates(() => this.countNumber()) }, 0) // 事件处理函数自带`batchedUpdates`,相当于上面的情况 // this.countNumber() } countNumber() { const num = this.state.number this.setState({ number: num + 1, }) console.log(this.state.number) this.setState({ number: num + 2, }) console.log(this.state.number) this.setState({ number: num + 3, }) console.log(this.state.number) } 复制代码
export default class ReactRoot { constructor(container) { // TODO... } render(element) { // RootFiber const current = this.current; // 申请当前的创建更新时间 const currentTime = DOMRenderer.requestCurrentTimeForUpdate(); // expirationTime 过期时间,可以代表着本次更新任务的优先级; // 不同事件触发的update会产生不同priority // 不同priority使fiber获得不同的expirationTime const expirationTime = DOMRenderer.computeExpirationForFiber(currentTime, current); // 创建更新 const update = createUpdate(expirationTime); // fiber.tag为HostRoot类型,payload为对应要渲染的ReactComponents(APP 组件) update.payload = {element}; enqueueUpdate(current, update); // 首次渲染会走这里,再次更新就直接创建更新对象然后开始调度 return DOMRenderer.scheduleUpdateOnFiber(current, expirationTime); } } 复制代码
老样子,我们还是直接先上流程图,根据流程再来看代码和注释;在阅读react源码的时候,是相当枯燥的,我们需要一点耐心慢慢解刨;
// 从当前fiber递归上去到root,再从root开始work更新 export function scheduleUpdateOnFiber(fiber, expirationTime) { // 注意是值越大,权限越大,和16.7相反了; // 向上冒泡更新,同时更新的过期时间(expirationTime)和子节点的过期时间 (childExpirationTime) // 这样做的原因是让整个fiber树上更新的最高优先级冒泡到root节点,进行更新 const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime); // root == FiberRoot if (!root) { return; } // 开始安排调度安排调度 ensureRootIsScheduled(root); } 复制代码
function ensureRootIsScheduled(root) { // 这个变量记录过期未执行的fiber的expirationTime const lastExpiredTime = root.lastExpiredTime; if (lastExpiredTime !== NoWork) { // ....TODO } // 寻找root(FiberRoot)本次更新的过期时间 const expirationTime = getNextRootExpirationTimeToWorkOn(root); const existingCallbackNode = root.callbackNode; // 本次更新的过期时间其实是没有任务 if (expirationTime === NoWork) { // 又存在当前正在进行的异步任务,同步执行掉 if (existingCallbackNode) { root.callbackNode = null; root.callbackExpirationTime = NoWork; root.callbackPriority = Scheduler.NoPriority; } return; } // 从当前时间和expirationTime推断任务优先级 const currentTime = requestCurrentTimeForUpdate(); const priorityLevel = inferPriorityFromExpirationTime(currentTime, expirationTime); if (existingCallbackNode) { // 该root上已存在schedule的root const existingCallbackNodePriority = root.callbackPriority; const existingCallbackExpirationTime = root.callbackExpirationTime; if (existingCallbackExpirationTime === expirationTime && existingCallbackNodePriority >= priorityLevel) { // 该root已经存在的任务expirationTime和新udpate产生的expirationTime一致 // 这代表他们可能是同一个事件触发产生的update // 且已经存在的任务优先级更高,则可以取消这次update的render return; } // 否则代表新udpate产生的优先级更高,取消之前的schedule,重新开始一次新的 Scheduler.cancelCallback(existingCallbackNode); } root.callbackExpirationTime = expirationTime; root.callbackPriority = priorityLevel; // 保存Scheduler保存的当前正在进行的异步任务 let callbackNode; // 过期任何和同步任务一样,不中断,一口气更新完; if (expirationTime === Sync) { callbackNode = scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } else { // 正常的异步任务和Concurrent首次渲染走走这里 callbackNode = Scheduler.scheduleCallback( priorityLevel, performConcurrentWorkOnRoot.bind(null, root), // 根据expirationTime,为任务计算一个timeout // timeout会影响任务执行优先级 {timeout: expirationTimeToMs(expirationTime) - Scheduler.now()} ) } root.callbackNode = callbackNode; } 复制代码
function performSyncWorkOnRoot(root) { const lastExpiredTime = root.lastExpiredTime; const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync; //先暂时忽略这个函数 flushPassiveEffects(); if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) { // 创建WIP树进行创建更新,如果WIP树还存在,说明需要打断这个任务 prepareFreshStack(root, expirationTime); } //根据WIP树进行更新 if (workInProgress) { const prevExecutionContext = executionContext; executionContext |= RenderContext; do { // 进入同步的workLoop渲染大循环 workLoopSync(); break; } while (true) // render阶段结束,进入commit阶段,commit阶段不可中断 commitRoot(root); // 重新安排调度, 以免又执行不到过期了的任务; ensureRootIsScheduled(root); } return null; } 复制代码
function workLoopSync() { while (workInProgress) { workInProgress = performUnitOfWork(workInProgress); } } 复制代码
function performUnitOfWork(unitOfWork) { const current = unitOfWork.alternate; // beginWork会返回fiber.child,不存在next意味着深度优先遍历已经遍历到某个子树的最深层叶子节点 // beginWork 为render阶段的主要工作之一,主要做了如下事: // 根据update更新 state // 根据update更新 props // 根据update更新 effectTag let next = beginWork(current, unitOfWork, renderExpirationTime); // beginWork完成 fiber的diff,可以更新momoizedProps unitOfWork.memoizedProps = unitOfWork.pendingProps; if (!next) { // completeUnitOfWork 主要做了如下事: // 1.为 beginWork阶段生成的fiber生成对应DOM,并产生DOM树 // let next = completeWork(current, workInProgress); // 2. 将child fiber的expirationTime冒泡到父级 // 这样在父级就能直到子孙中优先级最高到expirationTime // resetChildExpirationTime(workInProgress); // 3. 组装圣诞树链条 effect list next = completeUnitOfWork(unitOfWork); } return next; } 复制代码
function commitRoot(root) { const renderPriorityLevel = Scheduler.getCurrentPriorityLevel(); // 包裹一层commitRoot,commit使用Scheduler调度 Scheduler.runWithPriority(Scheduler.ImmediatePriority, commitRootImp.bind(null, root, renderPriorityLevel)); } // commit阶段的入口,包括如下子阶段: // before mutation阶段:遍历effect list,执行 DOM操作前触发的钩子 // mutation阶段:遍历effect list,执行effect function commitRootImp(root) { do { // syncCallback会保存在一个内部数组中,在 flushPassiveEffects 中 同步执行完 // 由于syncCallback的callback是 performSyncWorkOnRoot,可能产生新的 passive effect // 所以需要遍历直到rootWithPendingPassiveEffects为空 flushPassiveEffects(); } while (ReactFiberCommitWorkGlobalVariables.rootWithPendingPassiveEffects !== null) if (!finishedWork) { return null; } root.finishedWork = null; root.finishedExpirationTime = NoWork; // 重置Scheduler相关 root.callbackNode = null; root.callbackExpirationTime = NoWork; root.callbackPriority = Scheduler.NoPriority; // 已经在commit阶段,finishedWork对应的expirationTime对应的任务的处理已经接近尾声 // 让我们找找下一个需要处理的任务 // 在 completeUnitOfWork中有childExpirationTime的冒泡逻辑 // fiber树中高优先级的expirationTime会冒泡到顶上 // 所以 childExpirationTime 代表整棵fiber树中下一个最高优先级的任务对应的expirationTime const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(finishedWork); // 更新root的firstPendingTime,这代表下一个要进行的任务的expirationTime markRootFinishedAtTime(root, expirationTime, remainingExpirationTimeBeforeCommit); if (root === workInProgressRoot) { // 重置 workInProgress workInProgressRoot = null; workInProgress = null; renderExpirationTime = NoWork; } let firstEffect; if (root.effectTag) { // 由于根节点的effect list不含有自身的effect,所以当根节点本身存在effect时需要将其append 入 effect list if (finishedWork.lastEffect) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 根节点本身没有effect firstEffect = finishedWork.firstEffect; } let nextEffect; if (firstEffect) { // before mutation阶段 const prevExecutionContext = executionContext; executionContext |= CommitContext; nextEffect = firstEffect; do { try { nextEffect = commitBeforeMutationEffects(nextEffect); } catch(e) { console.warn('commit before error', e); nextEffect = nextEffect.nextEffect; } } while(nextEffect) // mutation阶段 nextEffect = firstEffect; do { try { nextEffect = commitMutationEffects(root, nextEffect); } catch(e) { console.warn('commit mutaion error', e); nextEffect = nextEffect.nextEffect; } } while(nextEffect) // workInProgress tree 现在完成副作用的渲染变成current tree // 之所以在 mutation阶段后设置是为了componentWillUnmount触发时 current 仍然指向之前那棵树 root.current = finishedWork; if (ReactFiberCommitWorkGlobalVariables.rootDoesHavePassiveEffects) { // 本次commit含有passiveEffect ReactFiberCommitWorkGlobalVariables.rootDoesHavePassiveEffects = false; ReactFiberCommitWorkGlobalVariables.rootWithPendingPassiveEffects = root; ReactFiberCommitWorkGlobalVariables.pendingPassiveEffectsExpirationTime = expirationTime; ReactFiberCommitWorkGlobalVariables.pendingPassiveEffectsRenderPriority = renderPriorityLevel; } else { // effectList已处理完,GC nextEffect = firstEffect; while (nextEffect) { const nextNextEffect = nextEffect.next; nextEffect.next = null; nextEffect = nextNextEffect; } } executionContext = prevExecutionContext; } else { // 无effect root.current = finishedWork; } } 复制代码
内容未完待续
待更新