认识
一、认识
useState
允许在函数组件中定义一个状态变量。useState
返回一个数组, 该数组中含有两个元素: 当前状态值和一个更新该状态的函数。useState
可以传入一个初始值或者一个回调函数, 回调函数会得到一个先前状态的参数。当调用更新状态的函数时, React
会将传入的值与当前状态进行 Object.is
比较, 如果状态发生变化, React
将会重新渲染组件。useState
与类组件中 this.setState
不同的是, useState
会直接替换之前值, 而不是合并。React
会对 useState
调用进行批量处理已优化性能。
二、细节
useState
Hook
大致结构为:
2.1 hook
const hook = {
next: null,
baseQueue: null,
baseState: null,
updateQueue: null,
memoizedState: null
};
2.2 update
export const createUpdate = (
action,
lane,
hasEagerState = false,
eagerState = null
) => {
return {
lane,
action,
next: null,
hasEagerState,
eagerState
};
};
2.3 updateQueue
function createFCUpdateQueue() {
const updateQueue = createUpdateQueue();
updateQueue.lastEffect = null;
return updateQueue;
}
export const createUpdateQueue = () => {
return {
shared: {
pending: null
},
dispatch: null
};
};
updateQueue.shared.pending
: 存储 update
对象
updateQueue.dispatch
: 存储 dispatchAction.bind()
的值
2.4 hook.updateQueue
hook.updateQueue
用于存储 updateQueue
2.5 hook.memoizedState
hook.memoizedState
用于存储 state
值
2.6 fiber.memoizedState
fiber.memoizedState
用于存储 hooks
链表
三、执行函数组件
在 React.js
的 beginWork
阶段, 遇到 FunctionComponent
类型的 Fiber
, 会执行 updateFunctionComponent
。updateFunctionComponent
内部调用 renderWithHooks
。主要工作如下:
-
重置状态: 函数组件执行前, 重置当前正在处理的
hook
、当前正在使用的hook
-
记录当前组件
fiber
: 通过currentlyRenderingFiber
变量记录当前组件fiber
-
选择对应阶段的
Hook
:mount
阶段选择HooksDispatcherOnMount
作为Hooks Map
,update
阶段选择HooksDispatcherOnUpdate
作为Hooks Map
。 函数组件mount
阶段 创建hook
数据结构, 存储到fiber.memoizedState
或者hook.next
, 初次建立其hooks
与fiber
之间的关系。函数组件update
阶段 找到当前函数组件fiber
, 找到当前hook
, 更新hook
状态。 -
执行函数组件: 执行我们真正函数组件,所有的
hooks
将依次执行, 每个hook
内部要根据currentlyRenderingFiber
读取对应的内容, -
重置状态: 执行完
Component
, 立马重置currentlyRenderingFiber
, 防止函数组件外部调用。置当前正在处理的hook
、当前正在使用的hook
3.1 /packages/react-reconciler/fiberHooks.js
export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
const current = workInProgress.alternate;
if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}
const props = workInProgress.pendingProps;
const children = Component(props);
currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;
return children;
}
3.2 /packages/react-reconciler/fiberHooks.js
/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};
/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};
四、函数组件 mount 阶段 Hook
useState
在 mount
阶段创建当前 hook
, 处理初始值, 初始化 updateQueue
, hook.updateQueue
保存已创建好的 updateQueue
, hook.memoizedState
存储初始值。通过 dispatchSetState
创建 dispatch
方法, 同 初始值
一同以数组的形式返回。
返回的形式是数组, 而不是对象: 因为数组解构时可以任意命名, 而对象解构需要属性名一致, 不方便。
创建 dispatchSetState
的主要逻辑为: dispatchSetState
通过 .bind
方法, 预先传入 fiber
与 updateQueue
, 当用户调用 dispatch
时, 传入一个值, 这个作为 action
参数传入。在 dispatchSetState
中通过 action
和当前优先级创建 update
数据结构, 随后进行 eagerState
优化策略, 如果前后数据无变化, 只需将 update
加入 updateQueue.shared.pending
中皆可, 并且更新优先级为 NoLane
。如果前后数据有变化, 在update
加入 updateQueue.shared.pending
的同时, 以传入的 Lane
优先级, 调用 scheduleUpdateOnFiber
开启调度更新流程。 其中 update
加入 updateQueue.shared.pending
中形成环状链表
4.1 mountState()
function mountState(initialState) {
const hook = mountWorkInProgressHook();
let memoizedState;
if (initialState instanceof Function) {
memoizedState = initialState();
} else {
memoizedState = initialState;
}
const queue = createFCUpdateQueue();
hook.updateQueue = queue;
hook.baseState = memoizedState;
hook.memoizedState = memoizedState;
const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;
queue.lastRenderedState = memoizedState;
return [memoizedState, dispatch];
}
4.2 createFCUpdateQueue()
function createFCUpdateQueue() {
const updateQueue = createUpdateQueue();
updateQueue.lastEffect = null;
return updateQueue;
}
4.3 dispatchSetState()
/**
* @description: dispatchSetState
* @param {*} fiber dispatchSetState.bind 时已经传入
* @param {*} updateQueue dispatchSetState.bind 时已经传入
* @param {*} action : setState(value) || setValue(()=> value) 中的 value 或者 ()=> value
*/
function dispatchSetState(fiber, updateQueue, action) {
const lane = requestUpdateLane();
const update = createUpdate(action, lane);
// eagerState 策略
const current = fiber.alternate;
if (
fiber.lanes === NoLanes &&
(current == null || current.lanes === NoLanes)
) {
const currentState = updateQueue.lastRenderedState;
const eagerState = basicStateReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (Object.is(currentState, eagerState)) {
enqueueUpdate(updateQueue, update, fiber, NoLane);
console.log('命中 eagerState 策略', fiber);
return;
}
}
enqueueUpdate(updateQueue, update, fiber, lane);
scheduleUpdateOnFiber(fiber, lane);
}
五、函数组件 update 阶段 Hook
useState
在 update
阶段找到对应的 hook
, 计算最新的 update
状态, 更新 hook.memoizedState
, 并返回。
计算 Update
状态, 主要逻辑如下:
-
获取上次更新时因优先级较低而跳过组成的链表的头结点
firstBaseUpdate
与尾节点lastBaseUpdate
, 同时将shared.pending
拆开拼接到尾节点lastBaseUpdate
的后面 -
设置
newState
用于存储Update
计算结果, 设置newBaseState
用于存储第一个低优先级任务之前的Update
计算结果newState
, 若不存在低优先级的任务,则newBaseState
为执行完所有任务后得到的值newState
。设置newLastBaseUpdate
用于存储低优先级的更新链表。 -
遍历
firstBaseUpdate
链表, 依次判断当前Update
对应的任务优先级是否足够 -
Update
优先级较低: 如果newLastBaseUpdate
为空, 说明此时的Update
是第一个低优先级任务, 更新newBaseState
为上一次Update
计算结果newState
。 如果newLastBaseUpdate
不为空, 将低优先级任务加入到newLastBaseUpdate
队列即可。 -
Update
优先级足够: 如果存储低优先级任务链表的newLastBaseUpdate
不为空, 为了保证操作的完整性, 将Update
加入到newLastBaseUpdate
后面。执行Update
, 获取最新计算结果newState
。 -
最后, 将执行所有操作后得到的
newState
赋值给fiber.memoizedState
。将newBaseState
赋值给updateQueue.baseState
, 作为下次的初始值。将newLastBaseUpdate
赋值给updateQueue.lastBaseUpdate
, 用于下次更新。
5.1 updateState()
function updateState() {
const hook = updateWorkInProgressHook();
const queue = hook.updateQueue;
const baseState = hook.baseState;
const pending = queue.shared.pending;
const current = currentHook;
let baseQueue = current.baseQueue;
if (pending != null) {
if (baseQueue != null) {
const baseFirst = baseQueue.next;
const pendingFirst = pending.next;
baseQueue.next = pendingFirst;
pending.next = baseFirst;
}
baseQueue = pending;
current.baseQueue = pending;
queue.shared.pending = null;
}
if (baseQueue !== null) {
const prevState = hook.memoizedState;
const {
memoizedState,
baseQueue: newBaseQueue,
baseState: newBaseState
} = processUpdateQueue(baseState, baseQueue, renderLane, update => {
const skippedLane = update.lane;
const fiber = currentlyRenderingFiber;
fiber.lanes = mergeLanes(fiber.lanes, skippedLane);
});
if (!Object.is(prevState, memoizedState)) {
markWorkInProgressReceiveUpdate();
}
hook.memoizedState = memoizedState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueue;
queue.lastRenderedState = memoizedState;
}
return [hook.memoizedState, queue.dispatch];
}
5.2 processUpdateQueue()
export const processUpdateQueue = (
baseState,
pendingUpdate,
renderLane,
onSkipUpdate
) => {
const result = { baseState, baseQueue: null, memoizedState: baseState };
if (pendingUpdate !== null) {
let first = pendingUpdate.next;
let pending = pendingUpdate.next;
let newBaseState = baseState;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let newState = baseState;
do {
const updateLane = pending.lane;
if (!isSubsetOfLanes(renderLane, updateLane)) {
const clone = createUpdate(pending.action, pending.lane);
onSkipUpdate?.(clone);
if (newBaseQueueFirst === null) {
newBaseQueueFirst = clone;
newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast.next = clone;
newBaseQueueLast = clone;
}
} else {
if (newBaseQueueLast !== null) {
const clone = createUpdate(pending.action, NoLane);
newBaseQueueLast.next = clone;
newBaseQueueLast = clone;
}
const action = pending.action;
if (pending.hasEagerState) {
newState = pending.eagerState;
} else {
newState = basicStateReducer(baseState, action);
}
}
pending = pending.next;
} while (pending !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
}
result.memoizedState = newState;
result.baseState = newBaseState;
result.baseQueue = newBaseQueueLast;
}
return result;
};
5.3 markWorkInProgressReceiveUpdate()
export function markWorkInProgressReceiveUpdate() {
didReceiveUpdate = true;
}