跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


useState 允许在函数组件中定义一个状态变量。useState 返回一个数组, 该数组中含有两个元素: 当前状态值和一个更新该状态的函数。useState 可以传入一个初始值或者一个回调函数, 回调函数会得到一个先前状态的参数。当调用更新状态的函数时, React 会将传入的值与当前状态进行 Object.is 比较, 如果状态发生变化, React 将会重新渲染组件。useState 与类组件中 this.setState 不同的是, useState 会直接替换之前值, 而不是合并。React 会对 useState 调用进行批量处理已优化性能。

二、细节


useState Hook 大致结构为:

Preview

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.jsbeginWork 阶段, 遇到 FunctionComponent 类型的 Fiber, 会执行 updateFunctionComponentupdateFunctionComponent 内部调用 renderWithHooks。主要工作如下:

  1. 重置状态: 函数组件执行前, 重置当前正在处理的 hook、当前正在使用的 hook

  2. 记录当前组件 fiber: 通过 currentlyRenderingFiber 变量记录当前组件 fiber

  3. 选择对应阶段的 Hook: mount 阶段选择 HooksDispatcherOnMount 作为 Hooks Map, update 阶段选择 HooksDispatcherOnUpdate 作为 Hooks Map函数组件mount 阶段 创建 hook 数据结构, 存储到 fiber.memoizedState 或者 hook.next, 初次建立其 hooksfiber 之间的关系。函数组件 update 阶段 找到当前函数组件 fiber, 找到当前 hook, 更新 hook 状态。

  4. 执行函数组件: 执行我们真正函数组件,所有的 hooks 将依次执行, 每个 hook 内部要根据 currentlyRenderingFiber 读取对应的内容,

  5. 重置状态: 执行完 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


useStatemount 阶段创建当前 hook, 处理初始值, 初始化 updateQueue, hook.updateQueue 保存已创建好的 updateQueue, hook.memoizedState 存储初始值。通过 dispatchSetState 创建 dispatch 方法, 同 初始值 一同以数组的形式返回。

返回的形式是数组, 而不是对象: 因为数组解构时可以任意命名, 而对象解构需要属性名一致, 不方便。

创建 dispatchSetState 的主要逻辑为: dispatchSetState 通过 .bind 方法, 预先传入 fiberupdateQueue, 当用户调用 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


useStateupdate 阶段找到对应的 hook, 计算最新的 update 状态, 更新 hook.memoizedState, 并返回。

计算 Update 状态, 主要逻辑如下:

  1. 获取上次更新时因优先级较低而跳过组成的链表的头结点 firstBaseUpdate 与尾节点 lastBaseUpdate, 同时将 shared.pending 拆开拼接到尾节点 lastBaseUpdate 的后面

  2. 设置 newState 用于存储 Update 计算结果, 设置 newBaseState 用于存储第一个低优先级任务之前的 Update 计算结果 newState, 若不存在低优先级的任务,则 newBaseState 为执行完所有任务后得到的值 newState。设置 newLastBaseUpdate 用于存储低优先级的更新链表。

  3. 遍历 firstBaseUpdate 链表, 依次判断当前 Update 对应的任务优先级是否足够

  4. Update 优先级较低: 如果 newLastBaseUpdate 为空, 说明此时的 Update 是第一个低优先级任务, 更新 newBaseState 为上一次 Update 计算结果 newState。 如果 newLastBaseUpdate 不为空, 将低优先级任务加入到 newLastBaseUpdate 队列即可。

  5. Update 优先级足够: 如果存储低优先级任务链表的 newLastBaseUpdate 不为空, 为了保证操作的完整性, 将 Update 加入到 newLastBaseUpdate 后面。执行 Update, 获取最新计算结果 newState

  6. 最后, 将执行所有操作后得到的 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;
}