一、初始化
构建dispatcher函数和初始值
二、更新时
- 调用dispatcher函数,按序插入update(其实就是一个action)
- 收集update,调度一次React的更新
- 在更新的过程中将
ReactCurrentDispatcher.current指向负责更新的Dispatcher - 执行到函数组件App()时,
useState会被重新执行,在resolve dispatcher的阶段拿到了负责更新的dispatcher。 useState会拿到Hook对象,Hook.queue中存储了更新队列,依次进行更新后,即可拿到最新的state- 函数组件App()执行后返回的nextChild中的count值已经是最新的了。FiberNode中的
memorizedState也被设置为最新的state - Fiber渲染出真实DOM。更新结束
三、 了解useState
useState的引入
// React.js
import { useState } from './ReactHooks';
所有的Hooks在React.js中被引入,挂载在React对象中
// ./ReactHooks.ts
export function useState<S>(initialState: (() => S) | S) {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
重点都在这个dispatcher上,dispatcher通过resolveDispatcher()来获取,这个函数同样也很简单,只是将ReactCurrentDispatcher.current的值赋给了dispatcher。
// ReactHooks.ts
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应该返回的结果的queue:缓存队列,存储多次更新行为next:指向下一次useState对应的Hook对象。
renderWithHooks
renderWithHooks的运行过程如下:
// ReactFiberHooks.ts
export function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
refOrContext: any,
nextRenderExpirationTime: ExpirationTime,
): any {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
// 如果current的值为空,说明还没有hook对象被挂载
// 而根据hook对象结构可知,current.memoizedState指向下一个current
nextCurrentHook = current !== null ? current.memoizedState : 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.memoizedState = firstWorkInProgressHook;
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的初始化工作
hook.memoizedState = hook.baseState = initialState;
// 进行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 [hook.memoizedState, 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是最近的一次更新,然后last.next开始是每一次的action
const last = queue.last;
if (last === null) {
// 只有一个update, 自己指自己-形成环
update.next = update;
} else {
const first = last.next;
if (first !== null) {
update.next = first;
}
last.next = update;
}
queue.last = update;
/** 省略特殊情况相关代码 **/
// 创建一个更新任务
scheduleWork(fiber, expirationTime);
}
dispatchAction中维护了一份query的数据结构。
query是一个有环链表,规则:
- query.last指向最近一次更新
- last.next指向第一次更新
- 后面就依次类推,最终倒数第二次更新指向last,形成一个环。
所以每次插入新update时,就需要将原来的first指向query.last.next。再将update指向query.next,最后将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);
}
// 可以看到updateReducer的过程与传的initalState已经无关了,所以初始值只在第一次被使用
// 为了方便阅读,删去了一些无关代码
// 查看完整代码:https://github.com/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 = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = hook.memoizedState;
let update = firstRenderPhaseUpdate;
// 获取更新后的state
do {
const action = update.action;
// 此时的reducer是basicStateReducer,直接返回action的值
newState = reducer(newState, action);
update = update.next;
} while (update !== null);
// 对 更新hook.memoized
hook.memoizedState = newState;
// 返回新的 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值。
扩展 - useState设计成数组的原因
主要包括保持API的一致性和简洁性、方便解构赋值、灵活性和兼容性。 以下是详细介绍:
- 保持API的一致性和简洁性。useState返回值是一个数组,这样可以方便地通过数组解构赋值方式获取和管理状态的值和更新函数,保持了React API的一致性。
- 方便解构赋值。useState返回的是数组,而不是对象,利用JavaScrip]的ES6解构赋值特性,可以方便地解构赋值,例如
const [count, setCount] = useState(0);,这样设计可以让开发者更容易地理解和学习。 - 灵活性和兼容性。使用数组作为返回值,其元素可以按次序排列,允许开发者在变量名上使用自定义的名字,提供更大的灵活性,并且这种设计可以更好地与React的其他Hook保持一致,也能与JavaScript的数组方法结合使用,例如map、filter等。
此外,这种设计还可以减少代码的重构成本,假设未来React团队需要改变useState的返回值类型,使用数组作为返回值类型可以在不破坏现有代码的情况下进行修改。