在具体说明之前,先放上一个例子。
function FunctionComponent() {
const [number, setNumber] = React.useReducer(reducer, 0);
return (
<button
onClick={() => {
setNumber({ type: "add", payload: 1 });
setNumber({ type: "add", payload: 2 });
setNumber({ type: "add", payload: 3 });
}}
>
{number}
</button>
);
}
根据上面这个例子,画出 mount 阶段完成后的完整 fiber 结构。如下图:
上图就是挂载阶段完成后的完整数据结构。从图中可以看到,这时的初始状态为 0。根据之前的学习,Fiber 节点有一个 alternate 属性指向他正在构建中的工作树(workInProgress)。而这个工作树就是需要实现的目标。
如下图:
目标就是实现这个数据结构并更新 hook 中的状态。
执行流程
之前已经说明挂载逻辑,接下来说明用户点击按钮,触发 setNumber 事件时的更新逻辑。
根据之前的说明了解到,setNumber 函数实际上是绑定了两个参数的 dispatchReducerAction 函数。这两个参数分别是 fiber 和 queue,也就是当前 fiber 和 对应的更新队列。setNumber 函数传入的为 update。这三个参数为一组,按顺序存储在 concurrentQueue 数组中。最后调用 scheduleUpdateOnFiber 函数。
scheduleUpdateOnFiber 函数的作用是渲染页面,调用这个函数也就意味着会开始重新构建 fiber 树,并且渲染。
根据一开始的 main.ts 示例代码,可以直接看对于函数式组件的处理逻辑。
/**
* 渲染函数组件
* @param {*} current 老 fiber
* @param {*} workInProgress 新 fiber
* @param {*} Component 组件
* @param {*} props 组件属性
* @returns 虚拟DOM或者说 React 元素
*/
export function renderWithHooks(current, workInProgress, Component, props) {
currentRenderingFiber = workInProgress;
if (current !== null && current.memoizedState !== null) {
// 更新
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// 挂载
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
// 需要在函数组件执行前,给 ReactCurrentDispatcher.current 赋值
const children = Component(props);
currentRenderingFiber = null;
workInProgressHook = null;
return children;
}
在真正执行组件函数之前,需要判断当前渲染是更新或者是挂载,以挂载不同的 hook 处理函数。这里肯定是更新,所以现在的 useReducer 函数就是 HooksDispatcherOnUpdate.useReducer,这个函数就是 updateReducer 函数。
接下来执行组件函数,在组件函数中执行 useReducer 函数,也就是 updateReducer。
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
// 获取新 hook 的更新队列
const queue = hook.queue;
// 获取老的 hook
const current = currentHook;
// 获取将要生效的更新队列
const pendingQueue = queue.pending;
// 初始化一个新状态,取值为当前状态
let newState = current.memoizedState;
if (pendingQueue !== null) {
queue.pending = null;
const firstUpdate = pendingQueue.next;
let update = firstUpdate;
do {
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== null && update !== firstUpdate);
}
hook.memoizedState = newState;
return [hook.memoizedState, queue.dispatch];
}
function updateWorkInProgressHook() {
// 获取将要构建的新的 hook 的老 hook
if (currentHook === null) {
const current = currentRenderingFiber.alternate;
currentHook = current.memoizedState;
} else {
currentHook = currentHook.next;
}
// 根据老 hook 创建新 hook
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
return workInProgressHook;
}
在 updateReducer 函数中,首先要构建一个新的 hook 链表。所以首先调用 updateWorkInProgressHook 函数得到一个新 hook。这个时候,这个新 hook 的 memoizedState 和 queue 属性都复用的老 hook的。接下来需要根据 hook 中的更新队列,计算出新的 memoizedState。其实就是循环执行更新队列。最后返回新的状态。
最后组件函数执行完毕,拿到新返回的虚拟 DOM,这个时候,number 值为 6。
拿到返回的更新后的虚拟 DOM 后,进入正常的更新渲染逻辑,将更新渲染到页面中去。