You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functiondispatchAction<S,A>(fiber: Fiber,queue: UpdateQueue<S,A>,action: A,){constalternate=fiber.alternate;// [情况一]: 如果产生更新的fiber = 当前正在渲染的fiberif(fiber===currentlyRenderingFiber||(alternate!==null&&alternate===currentlyRenderingFiber)){// This is a render phase update. Stash it in a lazily-created map of// queue -> linked list of updates. After this render pass, we'll restart// and apply the stashed updates on top of the work-in-progress hook.// 标识是否在`mount`阶段产生更新// 产生的更新会保存到`renderPhaseUpdates`字典上// 后续在渲染完当前组件之后, 会根据`didScheduleRenderPhaseUpdate`, 来决定是否处理更新didScheduleRenderPhaseUpdate=true;constupdate: Update<S,A>={expirationTime: renderExpirationTime,
action,eagerReducer: null,eagerState: null,next: null,};if(renderPhaseUpdates===null){renderPhaseUpdates=newMap();}constfirstRenderPhaseUpdate=renderPhaseUpdates.get(queue);if(firstRenderPhaseUpdate===undefined){renderPhaseUpdates.set(queue,update);}else{// Append the update to the end of the list.letlastRenderPhaseUpdate=firstRenderPhaseUpdate;while(lastRenderPhaseUpdate.next!==null){lastRenderPhaseUpdate=lastRenderPhaseUpdate.next;}lastRenderPhaseUpdate.next=update;}}// [情况二]: 反之, 如果产生更新的不是currentRenderingFiber, 那么调用scheduleWork重新调度else{flushPassiveEffects();constcurrentTime=requestCurrentTime();constexpirationTime=computeExpirationForFiber(currentTime,fiber);constupdate: Update<S,A>={
expirationTime,
action,eagerReducer: null,eagerState: null,next: null,};// Append the update to the end of the list.constlast=queue.last;if(last===null){// This is the first update. Create a circular list.update.next=update;}else{constfirst=last.next;if(first!==null){// Still circular.update.next=first;}last.next=update;}queue.last=update;scheduleWork(fiber,expirationTime);}}
functionupdateReducer<S,I,A>(reducer: (S,A)=>S,initialArg: I,init?: I=>S,): [S,Dispatch<A>]{
const hook=updateWorkInProgressHook();constqueue=hook.queue;invariant(queue!==null,'Should have a queue. This is likely a bug in React. Please file an issue.',);queue.lastRenderedReducer=reducer;// ! numberOfReRenders记录组件重新渲染的次数if(numberOfReRenders>0){// This is a re-render. Apply the new render phase updates to the previous// work-in-progress hook.constdispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {// Render phase updates are stored in a map of queue -> linked listconstfirstRenderPhaseUpdate=renderPhaseUpdates.get(queue);if(firstRenderPhaseUpdate!==undefined){renderPhaseUpdates.delete(queue);letnewState=hook.memoizedState;letupdate=firstRenderPhaseUpdate;do{// Process this render phase update. We don't have to check the// priority because it will always be the same as the current// render's.constaction=update.action;newState=reducer(newState,action);update=update.next;} while (update !== null);
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {markWorkInProgressReceivedUpdate();}
hook.memoizedState = newState;
// Don't persist the state accumlated from the render phase updates to
// the base state unless the queue is empty.
// TODO: Not sure if this is the desired semantics, but it's what we
// do for gDSFP. I can't remember why.
if (hook.baseUpdate === queue.last) {hook.baseState=newState;}
queue.lastRenderedState = newState;
return [newState, dispatch];
}}return[hook.memoizedState,dispatch];}// ! hook.queue为单向循环链表结构// The last update in the entire queueconstlast=queue.last;// The last update that is part of the base state.// ? baseUpdate记录当前被中断(优先级不够)的更新的上一个更新// ? baseState记录被中断前计算出来的state// ? 这是由于当前queue.update优先级不足, 所以需要记录一个位置,// ? 等到下一次react应用整体更新时重新处理更新constbaseUpdate=hook.baseUpdate;constbaseState=hook.baseState;// Find the first unprocessed update.// ! 找到第一个未处理的更新letfirst;if(baseUpdate!==null){if(last!==null){// For the first update, the queue is a circular linked list where// `queue.last.next = queue.first`. Once the first update commits, and// the `baseUpdate` is no longer empty, we can unravel the list.last.next=null;}first=baseUpdate.next;}else{first=last!==null ? last.next : null;}if(first!==null){letnewState=baseState;letnewBaseState=null;letnewBaseUpdate=null;letprevUpdate=baseUpdate;letupdate=first;letdidSkip=false;// ! 从queue中最后一个update开始, 遍历整个循环链表do{constupdateExpirationTime=update.expirationTime;// ! 如果queue.update优先级 < 当前渲染进程的优先级if(updateExpirationTime<renderExpirationTime){// Priority is insufficient. Skip this update. If this is the first// skipped update, the previous update/state is the new base// update/state.if(!didSkip){didSkip=true;newBaseUpdate=prevUpdate;newBaseState=newState;}// Update the remaining priority in the queue.if(updateExpirationTime>remainingExpirationTime){remainingExpirationTime=updateExpirationTime;}}else{// Process this update.if(update.eagerReducer===reducer){// If this update was processed eagerly, and its reducer matches the// current reducer, we can use the eagerly computed state.newState=((update.eagerState: any): S);}else{constaction=update.action;newState=reducer(newState,action);}}prevUpdate=update;update=update.next;}while(update!==null&&update!==first);if(!didSkip){newBaseUpdate=prevUpdate;newBaseState=newState;}// Mark that the fiber performed work, but only if the new state is// different from the current state.if(!is(newState,hook.memoizedState)){markWorkInProgressReceivedUpdate();}hook.memoizedState=newState;hook.baseUpdate=newBaseUpdate;hook.baseState=newBaseState;queue.lastRenderedState=newState;}constdispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
functiondispatchAction<S,A>(fiber: Fiber,queue: UpdateQueue<S,A>,action: A,){constalternate=fiber.alternate;// [情况一]: 如果产生更新的fiber = 当前正在渲染的fiberif(fiber===currentlyRenderingFiber||(alternate!==null&&alternate===currentlyRenderingFiber)){// This is a render phase update. Stash it in a lazily-created map of// queue -> linked list of updates. After this render pass, we'll restart// and apply the stashed updates on top of the work-in-progress hook.// 标识是否在`mount`阶段产生更新// 产生的更新会保存到`renderPhaseUpdates`字典上// 后续在渲染完当前组件之后, 会根据`didScheduleRenderPhaseUpdate`, 来决定是否处理更新didScheduleRenderPhaseUpdate=true;constupdate: Update<S,A>={expirationTime: renderExpirationTime,
action,eagerReducer: null,eagerState: null,next: null,};if(renderPhaseUpdates===null){renderPhaseUpdates=newMap();}constfirstRenderPhaseUpdate=renderPhaseUpdates.get(queue);if(firstRenderPhaseUpdate===undefined){renderPhaseUpdates.set(queue,update);}else{// Append the update to the end of the list.letlastRenderPhaseUpdate=firstRenderPhaseUpdate;while(lastRenderPhaseUpdate.next!==null){lastRenderPhaseUpdate=lastRenderPhaseUpdate.next;}lastRenderPhaseUpdate.next=update;}}// [情况二]: 反之, 如果产生更新的不是currentRenderingFiber, 那么调用scheduleWork重新调度// 创建更新, 加入到updateQueue, 重新调度// react应用每产生一次更新, 没有特殊情况下, 都会调用scheduleWorkelse{flushPassiveEffects();constcurrentTime=requestCurrentTime();constexpirationTime=computeExpirationForFiber(currentTime,fiber);constupdate: Update<S,A>={
expirationTime,
action,eagerReducer: null,eagerState: null,next: null,};// Append the update to the end of the list.constlast=queue.last;if(last===null){// This is the first update. Create a circular list.update.next=update;}else{constfirst=last.next;if(first!==null){// Still circular.update.next=first;}last.next=update;}queue.last=update;scheduleWork(fiber,expirationTime);}}
前言
俗话说, 工欲善其事必先利其器.
看完函数组件的更新源码, 自然而然地对于
hooks
的实现原理产生了浓厚的兴趣, 故记录在此.从如何使用说起
hooks
的出现, 完全颠覆了传统的class
至上的原则, 解决了代码冗余
、可维护性
、编译性能
等多个问题.看源码之前, 先看一下
hooks
是如何使用的:展开源码
在源码
TestHook
组件中, 按照顺序依次调用了三个hooks
API, 页面正常更新并渲染. 关于useState
、useEffect
的使用, 不记录太多了, 官方文档已经解释的非常清楚:https://react.docschina.org/docs/hooks-intro.html
品内部实现原理
复习了
hooks
的基本使用, 着重看下它是如何实现的. 由于目前对于源码的理解还不是很深刻, 故本篇笔记会持续更新.由于
hooks
的执行, 分为mount
和update
两个阶段. 前者在react应用初次渲染时执行, 后者则在组件全部挂载完成, 用户自定义操作(dispatch
)时执行. 由于执行时机不同, 故内部的源码实现逻辑则大相径庭. 所以会分成两个部分来记录.1. mount阶段
useState
以最基本的
useState
为例, 其源码位于ReactHooks.js
, 出乎意料的简单, 只有两行代码:接着看到
resolveDispatcher
:没错, 很熟悉, 依稀记得在更新
function
组件之时, 通过renderWithHooks
方法, 将ReactCurrentDispatcher.current
赋值为HooksDispatcherOnMount
.mountState
省去了一大堆重复的步骤, 直接来看
useState
的最终定义. 由于hooks
的执行分为mount
和update
阶段, 两者采取的更新策略略有不同, 这里只记录mount
的过程.展开源码
从源码可以看出,
hooks
的内部还是比较绕的. 但是不影响理解, 由于目前处于mount
阶段, 所以需要初始化相关的数据结构结构(有关hooks
是如何存储的, 可参考#10):而正常情况下, 会在
update
阶段产生更新, 最常见的就是用户交互(interactive
)时, 用上述例子来说:setName
或setAge
来产生一个更新所以将重点放在
update
阶段的分析.FAQ
mount
, 也就是首次渲染阶段, 在当前渲染的函数
组件内部产生更新和在其它组件内部产生更新有什么区别?mount
阶段的的dispatch
实际上调用了dispatchAction
方法, 其内部根据根据产生更新的fiber
, 进行不同的处理:mount
阶段, 且在其内部产生了更新mount
阶段, 但不是它内部产生的更新, 新的调度(scheduleWork
)看下
dispatchAction
的源码:展开源码
2. update阶段
update
阶段产生的更新与mount
稍有不同.ReactCurrentDispatcher
首先, 将
ReactCurrentDispatcher
的值由HooksDispatcherOnMount
变成了HooksDispatcherOnUpdate
:展开源码
updateState
内部实现较简单:
展开源码
updateReducer
遍历整个
hook.queue.update
, 根据basicStaticReducer
计算出新的state
,展开源码
dispatchAction
重点来了, 回想一下, 在初次渲染的时候, 也就是在
mountState
时, 会初始化hook.dispatch
:后续所有在当前
hooks
上的dispatch
, 都会调用dispatchAction
函数, 再来看一下其内部逻辑:展开源码
hook
在react的
函数
组件内部, 每定义一个hooks
API, 会产生新的hook
对象, 追加到fiber.memorizedState
链表中, 关于更多的有关于hook
的存储结构的分析, 我抽离出了新的issue
:解以前心头之惑
redux
看了
React.useState
的源码, 才发现其设计思想与redux
极其类似, 或者说它完全借鉴了redux
的策略:dispatch
一个action
reducer
处理得到新的state
state
didScheduleRenderPhaseUpdate
这是我比较困惑的全局变量之一.
起什么作用
渲染完当前
fiber
之后, 根据其值是否为true
, 来清理掉在渲染阶段产生的更新在何处使用
在
renderWithHooks
开始, 根据其值决定是否重新执行该组件在何处改变
当调用
dispatch
之时, 在dispatchAction
内部, 如果产生更新的fiber
和当前正在渲染的fiber
相同, 那么表明在渲染阶段产生了更新, 将didScheduleRenderPhaseUpdate
设置为true
.The text was updated successfully, but these errors were encountered: