useReducer 是一个函数。接收两个参数。reducer 函数和 state 初始状态。返回状态和回掉函数。
function reducer(state, action) {
if (type === "add") {
return state + 1;
}
return state;
}
function FunctionComponent() {
const [number, setNumber] = React.useReducer(reducer, 0);
return (
<div>
<button
onClick={() => {
setNumber({ type: "add" });
}}
>
+
</button>
<span>{number}</span>
</div>
);
}
实现
在 react 中,实现 useReducer 时做了很多变量转发,但是在挂载阶段,实际上就是执行了 mountReducer 函数。这个函数主要功能是对 hook 进行初始化,并返回初始状态和回掉函数。其中调用了 mountWorkInProgressHook 函数,主要作用是构建一个单向 hook 链表,并将当前 fiber 的 memoizedState 字段指向第一个 hook。
function mountReducer(reducer, initialArg) {
const hook = mountWorkInProgressHook();
hook.memoizedState = initialArg;
const queue = {
pending: null,
dispatch: null,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchReducerAction.bind(null, currentRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
/**
* 挂载构建中的 Hook
* @returns
*/
function mountWorkInProgressHook() {
const hook = {
memoizedState: null, // 缓存的 state, hook 状态
queue: null, // 存放本 hook 的更新队列
next: null, // 指向下一个 hook 单向链表
};
if (workInProgressHook === null) {
// 当前函数对应的 fiber 的状态等于第一个 hook
currentRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
这时,当初次挂载完成,用户点击按钮,调用 setNumber 时,执行的就是 dispatch 函数,也就是 dispatchReducerAction 函数。这个函数的作用是处理当前 hook 的更新队列,也就是初始化更新对象并将更新对象绑定到对应的更新队列。这里的绑定是通过数组实现的,数组中每三个元素为一组,分别为 fiber,更新队列,更新对象。最后调用 scheduleUpdateOnFiber 函数,渲染数据。
/**
* 执行派发动作的方法,他要更新状态,并且让页面重新更新
* @param {*} fiber function 对应的 fiber
* @param {*} queue hook 的更新队列
* @param {*} action 派发的动作
*/
function dispatchReducerAction(fiber, queue, action) {
// 每个 Hook 对应一个更新链表
const update = {
action,
next: null,
};
// 把当前最新的更新添加到更新队列中,并且返回当前的根 fiber
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root);
}
/**
* 把更新对象添加到更新队列中
* @param {*} fiber 函数组件对应 fiber
* @param {*} queue 要更新 hook 更新队列
* @param {*} update 更新对象
*/
export function enqueueConcurrentHookUpdate(fiber, queue, update) {
enqueueUpdate(fiber, queue, update);
return getRootForUpdatedFiber(fiber);
}
function getRootForUpdatedFiber(sourceFiber) {
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}
function enqueueUpdate(fiber, queue, update) {
concurrentQueue[concurrentQueueIndex++] = fiber;
concurrentQueue[concurrentQueueIndex++] = queue;
concurrentQueue[concurrentQueueIndex++] = update;
}