在上一篇文章中,我们讨论了函数式组件的构建和更新过程。而本文将深入探讨React hooks的工作原理。
首先,让我们理解一下hooks的分类:
- 状态Hook (State Hook): 如
useState和useReducer - 副作用Hook (Effect Hook): 如
useEffect和useLayoutEffect - Ref Hook: 如
useRef
而本文将讲状态Hook,useState和useReducer的实现。
2. useState的实现
useState是React中最常用的Hook之一。它的实现分为两个主要函数:mountState(用于初始化)和updateState(用于更新)。让我们先来看mountState的实现。
2.1 mountState
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
让我们逐行分析这个函数:
const hook = mountWorkInProgressHook();这行代码创建了一个新的hook对象,并将其添加到当前fiber的hooks链表中。这是React管理多个hooks的关键机制。if (typeof initialState === 'function') { initialState = initialState() }这是一个优化,允许用户传入一个函数来计算初始状态。这对于复杂或昂贵的初始状态计算很有用,因为它只会在组件挂载时执行一次。
hook.memoizedState = hook.baseState = initialState;这里初始化了hook的memoizedState和baseState。memoizedState存储当前状态,而baseState用于在更新过程中计算新状态。const queue = (hook.queue = { pending: null, dispatch: null, lastRenderedReducer: basicStateReducer, lastRenderedState: (initialState: any), });这创建了一个更新队列。
pending用于存储待处理的更新,dispatch是更新函数,lastRenderedReducer是最后一次渲染使用的reducer(对于useState,这是一个基本的状态更新函数),lastRenderedState是最后一次渲染的状态。const dispatch: Dispatch< BasicStateAction<S>, > = (queue.dispatch = (dispatchAction.bind( null, currentlyRenderingFiber, queue, ): any));这创建了
dispatch函数。它实际上是dispatchAction函数的绑定版本,预先绑定了当前fiber和更新队列。这是一个优化,避免了每次调用dispatch时都要重新绑定这些参数。return [hook.memoizedState, dispatch];最后返回当前状态和dispatch函数,这就是我们在使用useState时得到的数组。
2.2 dispatchAction
接下来,让我们看看dispatchAction函数,这是状态更新的核心:
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber);
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
const pending = queue.pending;
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
}
}
}
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
让我们逐步分析这个函数:
const eventTime = requestEventTime() const lane = requestUpdateLane(fiber)这两行代码获取了当前的事件时间和更新优先级(lane)。React使用lane模型来管理更新的优先级,这是一个重要的优化机制。
const update: Update<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null: any), };这创建了一个新的更新对象。
lane表示更新的优先级,action是状态更新的动作,eagerReducer和eagerState用于优化,next用于链接到下一个更新。const pending = queue.pending if (pending === null) { update.next = update } else { update.next = pending.next pending.next = update } queue.pending = update这段代码将新的更新添加到更新队列中。注意这里使用了环形链表的结构,这使得React可以高效地处理多个更新。
if (fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)) { didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true } else { // ... (优化逻辑) }这段代码检查是否在渲染阶段调度了更新。如果是,它会设置一个标志,以便React可以在当前渲染完成后立即开始新的渲染。
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) { // ... (优化逻辑) }这是一个重要的优化。如果当前fiber没有待处理的更新,React会尝试立即计算新的状态。
const lastRenderedReducer = queue.lastRenderedReducer if (lastRenderedReducer !== null) { // ... (优化逻辑) }这里React尝试使用上一次渲染的reducer来计算新状态。这是一个优化,可以避免不必要的重新渲染。
const currentState: S = (queue.lastRenderedState: any); const eagerState = lastRenderedReducer(currentState, action); update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { return; }这段代码计算了新的状态(
eagerState)。如果新状态与当前状态相同,函数直接返回,避免了不必要的更新。scheduleUpdateOnFiber(fiber, lane, eventTime);如果需要更新,这行代码会调度一个更新。
2.3 updateState
当组件更新时,会调用updateState。这个函数实际上是调用了updateReducer:
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
updateReducer是一个复杂的函数,它处理了状态的实际更新。让我们看看它的关键部分:
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
// ... (处理baseQueue和pendingQueue)
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不够,跳过这个更新
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
} else {
// 优先级足够,应用这个更新
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
if (update.eagerReducer === reducer) {
// 如果有缓存的计算结果,直接使用
newState = ((update.eagerState: any): S);
} else {
// 否则,调用reducer计算新状态
const action = update.action;
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
// ... (更新hook的状态)
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
这个函数的核心是一个循环,它遍历所有待处理的更新。让我们逐步分析:
const hook = updateWorkInProgressHook();这行代码获取当前正在工作的hook。React使用链表结构来管理一个组件中的多个hooks,这允许hooks在渲染过程中保持稳定的顺序。queue.lastRenderedReducer = reducer;更新队列中存储的最后一次渲染使用的reducer。这是为了后续的优化。接下来的大循环是整个函数的核心,它遍历所有待处理的更新。
if (!isSubsetOfLanes(renderLanes, updateLane)) { ... }这个条件检查当前更新的优先级是否足够高。如果优先级不够,这个更新会被跳过,并被添加到一个新的队列中,等待下次渲染。if (update.eagerReducer === reducer) { ... }这是一个重要的优化。如果当前更新已经有了预计算的结果(eagerState),并且使用的reducer没有变,那么就直接使用预计算的结果,避免重复计算。newState = reducer(newState, action);如果没有预计算的结果,就调用reducer计算新的状态。
这个函数展示了React在状态更新过程中的几个关键优化:
- 优先级调度:通过lane模型,React可以跳过低优先级的更新,优先处理高优先级的更新。
- 批量更新:多个更新被放在一个队列中一起处理,提高了效率。
- 预计算:通过eagerReducer和eagerState,React可以在某些情况下避免重复计算。
3. useReducer的实现
useReducer的实现与useState非常相似。主要区别在于,useReducer允许用户提供自定义的reducer函数。让我们看看mountReducer的实现:
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
这个函数与mountState非常相似,主要区别在于:
- 它接受一个
reducer函数作为参数。 - 它允许通过
init函数来初始化状态,这提供了更灵活的初始化方式。
useReducer的更新过程(updateReducer)与useState完全相同,我们在前面已经详细分析过了。
4. 深入思考
Hooks的本质:Hooks本质上是一种状态管理的方式。它们允许函数组件拥有自己的状态,而不需要转换为类组件。这大大简化了React组件的编写。
链表结构:React使用链表来管理hooks,这允许hooks在多次渲染之间保持稳定的顺序。这也是为什么hooks不能在条件语句中使用的原因。
批量更新和优先级:React的更新机制非常精细。它可以将多个更新批量处理,并且可以根据优先级来决定哪些更新应该先被处理。这大大提高了应用的性能和响应速度。
懒初始化:
useState和useReducer都支持懒初始化,即可以传入一个函数来计算初始状态。这对于复杂的初始状态计算很有用,可以避免在每次渲染时都重新计算。闭包陷阱:Hooks的实现依赖于JavaScript的闭包机制。这可能导致一些难以察觉的bug,比如在异步操作中使用旧的状态。理解这一点对于正确使用hooks非常重要。