useState原理解析
⼀、初始化
构建dispatcher函数和初始值
⼆、更新时
1. 调⽤dispatcher函数,按序插⼊update(其实就是⼀个action)
2. 收集update,调度⼀次React的更新
3. 在更新的过程中将ReactCurrentDispatcher.current指向负责更新的Dispatcher
4. 执⾏到函数组件App()时,useState会被重新执⾏,在resolve dispatcher的阶段拿到了负责更新的dispatcher。
5. useState会拿到Hook对象,Hook.query中存储了更新队列,依次进⾏更新后,即可拿到最新的state
6. 函数组件App()执⾏后返回的nextChild中的count值已经是最新的了。FiberNode中的memorizedState也被设置为最新的state
7. Fiber渲染出真实DOM。更新结束
三、了解useState
useState的引⼊
// React.js
import {
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
} from './ReactHooks';
所有的Hooks在React.js中被引⼊,挂载在React对象中
useState的实现
// ReactHooks.js
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
重点都在这个dispatcher上,dispatcher通过resolveDispatcher()来获取,这个函数同样也很简单,只是将ReactCurrentDispatcher.current的值赋给了dispatcher。
// ReactHooks.js
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
ReactCurrentDispatcher.current.useState是useState能够触发更新的关键原因,这个⽅法的实现并不在react包内。
四. 核⼼步骤分析
ReactFiberHooks.js包含着各种关于Hooks逻辑的处理
Hook对象的结构如下:
// ReactFiberHooks.js
export type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
在类组件中state是⼀整个对象,可以和memoizedState⼀⼀对应。但是在Hooks中,React并不知道我们调⽤了⼏次useState,所以React通过将⼀个Hook对象挂载在memorizedStated上来保存函数组件的state
重点关注memoizedState和next
memoizedState是⽤来记录当前useState应该返回的结果的
query:缓存队列,存储多次更新⾏为
next:指向下⼀次useState对应的Hook对象。
renderWithHooks
renderWithHooks的运⾏过程如下:
// ReactFiberHooks.js
export function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
refOrContext: any,
nextRenderExpirationTime: ExpirationTime,
): any {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
// 如果current的值为空,说明还没有hook对象被挂载
// ⽽根据hook对象结构可知,izedState指向下⼀个current
nextCurrentHook = current !== null ? izedState : null;
// ⽤nextCurrentHook的值来区分mount和update,设置不同的dispatcher
ReactCurrentDispatcher.current =
nextCurrentHook === null
// 初始化时
HooksDispatcherOnMount
// 更新时
: HooksDispatcherOnUpdate;
// 此时已经有了新的dispatcher,在调⽤Component时就可以拿到新的对象
let children = Component(props, refOrContext);
// 重置
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
const renderedWork: Fiber = (currentlyRenderingFiber: any);
// 更新memoizedState和updateQueue
renderedWork.updateQueue = (componentUpdateQueue: any);
/** 省略与本⽂⽆关的部分代码,便于理解 **/
}
初始化时
核⼼:创建⼀个新的hook,初始化state,并绑定触发器
初始化阶段ReactCurrentDispatcher.current会指向HooksDispatcherOnMount对象
// ReactFiberHooks.js
const HooksDispatcherOnMount: Dispatcher = {
/** 省略其它Hooks **/
useState: mountState,
};
// 所以调⽤useState(0)返回的就是HooksDispatcherOnMount.useState(0),也就是mountState(0)
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 访问Hook链表的下⼀个节点,获取到新的Hook对象
const hook = mountWorkInProgressHook();
//如果⼊参是function则会调⽤,但是不提供参数
if (typeof initialState === 'function') {
initialState = initialState();
}
/
/ 进⾏state的初始化⼯作
// 进⾏queue的初始化⼯作
const queue = (hook.queue = {
last: null,
dispatch: null,
eagerReducer: basicStateReducer, // useState使⽤基础reducer
eagerState: (initialState: any),
});
// 返回触发器
const dispatch: Dispatch<BasicStateAction<S>,>
= (queue.dispatch = (dispatchAction.bind(
null,
//绑定当前fiber结点和queue
((currentlyRenderingFiber: any): Fiber),
queue,
));
// 返回初始state和触发器
return [izedState, dispatch];
}
// 对于useState触发的update action来说(假设useState⾥⾯都传的变量),basicStateReducer就是直接返回action的值
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
更新函数dispatchAction
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
/** 省略Fiber调度相关代码 **/
// 创建新的新的update, action就是我们setCount⾥⾯的值(count+1, count+2, count+3…)
const update: Update<S, A> = {
expirationTime,
action,
eagerReducer: null,
eagerState: null,
next: null,
};
// 重点:构建query
// queue.last是最近的⼀次更新,然后开始是每⼀次的action
const last = queue.last;
if (last === null) {
// 只有⼀个update, ⾃⼰指⾃⼰-形成环
< = update;
} else {
const first = ;
if (first !== null) {
< = first;
}
< = update;
}
queue.last = update;
/** 省略特殊情况相关代码 **/
// 创建⼀个更新任务
scheduleWork(fiber, expirationTime);
}
dispatchAction中维护了⼀份query的数据结构。
query是⼀个有环链表,规则:
query.last指向最近⼀次更新
<指向第⼀次更新
后⾯就依次类推,最终倒数第⼆次更新指向last,形成⼀个环。
所以每次插⼊新update时,就需要将原来的first指向。再将update指向,最后将query.last指向update.更新时
核⼼:获取该Hook对象中的 queue,内部存有本次更新的⼀系列数据,进⾏更新
更新阶段ReactCurrentDispatcher.current会指向HooksDispatcherOnUpdate对象
// ReactFiberHooks.js
// 所以调⽤useState(0)返回的就是HooksDispatcherOnUpdate.useState(0),也就是updateReducer(basicStateReducer, 0)
const HooksDispatcherOnUpdate: Dispatcher = {
/** 省略其它Hooks **/
useState: updateState,
}
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
reacthooks理解// 可以看到updateReducer的过程与传的initalState已经⽆关了,所以初始值只在第⼀次被使⽤
// 为了⽅便阅读,删去了⼀些⽆关代码
/
/ 查看完整代码:github/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L606 function updateReducer(reducer, initialArg, init) {
// 获取初始化时的 hook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// 开始渲染更新
if (numberOfReRenders > 0) {
const dispatch = queue.dispatch;
if (renderPhaseUpdates !== null) {
// 获取Hook对象上的 queue,内部存有本次更新的⼀系列数据
const firstRenderPhaseUpdate = (queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = izedState;
let update = firstRenderPhaseUpdate;
// 获取更新后的state
do {
const action = update.action;
// 此时的reducer是basicStateReducer,直接返回action的值
newState = reducer(newState, action);
update = ;
} while (update !== null);
/
/ 对更新ized
// 返回新的 state,及更新 hook 的 dispatch ⽅法
return [newState, dispatch];
}
}
}
// 对于useState触发的update action来说(假设useState⾥⾯都传的变量),basicStateReducer就是直接返回action的值
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
总结
单个hooks的更新⾏为全都挂在Hooks.queue下,所以能够管理好queue的核⼼就在于
初始化queue - mountState
维护queue - dispatchAction
更新queue - updateReducer
结合⽰例代码:
当我们第⼀次调⽤[count, setCount] = useState(0)时,创建⼀个queue
每⼀次调⽤setCount(x),就dispach⼀个内容为x的action(action的表现为:将count设为x),action存储在queue中,以前⾯讲述的有环链表规则来维护
这些action最终在updateReducer中被调⽤,更新到memorizedState上,使我们能够获取到最新的state值。
⽂章就分享到这,欢迎关注“前端⼤神之路”