useReducer
function counter(state, action) {
if (action.type === "add") return state + action.payload;
return state;
}
const App = () => {
const [number, setNumber] = React.useReducer(counter, 0);
<button
{...attrs}
onClick={() => {
setNumber({ type: "add", payload: 1 }); //update1=>update2=>update3=>update1
setNumber({ type: "add", payload: 2 }); //update2
setNumber({ type: "add", payload: 3 }); //update3
}}
>
{number}
</button>;
};
实现原理
- hooks只存在于函数组件中,而函数组件的初始化阶段,我们需要执行它,然后拿到子节点
- renderWithHooks用来执行函数组件,并返回子节点
const HooksDispatcherOnMount = {
useReducer: mountReducer
}
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, currentlyRenderingFiber, queue));
return [hook.memoizedState, dispatch];
}
function mountWorkInProgressHook() {
const hook = {
memoizedState: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function dispatchReducerAction(fiber, queue, action) {
const update = {
action,
next: null
}
const root = enqueueConcurrentHookUpdate(fiber, queue, update);
scheduleUpdateOnFiber(root);
}
export function renderWithHooks(current, workInProgress, Component, props) {
currentlyRenderingFiber = workInProgress;
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount;
}
const children = Component(props);
currentlyRenderingFiber = null;
workInProgressHook = null;
currentHook = null;
return children;
}
- 可以看到ReactCurrentDispatcher.current里存了一个object,里面的useReducer为一个函数
它是React中一个内部变量的引用,它跟useReducer有直接的关联
- 现在就到了执行的时候了,我们需要去看React.useReducer的声明
const ReactCurrentDispatcher = {
current: null
}
export default ReactCurrentDispatcher;
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
function resolveDispatcher() {
return ReactCurrentDispatcher.current;
}
export function useReducer(reducer, initialArg) {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg);
}
- 现在就可以看出作用了,
React.useReducer === ReactCurrentDispatcher.current.useReducer
hooks初始阶段
- 执行mountWorkInProgressHook
- 函数组件fiber的memoizedState始终指向第一个hooks实例
- hook实例本身也是一个链表
const hook = {
memoizedState: null,
queue: {
pending: null,
dispatch: null
},
next: null
};
- 然后给hook.memoizedState赋值,给hook.queue.dispatch赋值
dispatch的作用是入队列和触发更新
- 返回
[hook.memoizedState, hook.queue.dispatch]
- 可以看到,
在首次初始化的时候,传入的reducer并未使用
如果是多个hook,就是next向下连接
- hook初始化完成了,函数就可以拿到值了,然后执行函数,函数执行完毕,清除闭包里的currentlyRenderingFiber和workInProgressHook,因为是公用的,防止被污染
更新
- 更新触发了上面的dispatch,进行了入队处理
- 这里存储的不是链表了,而是数组,每次向数组内加3项,分别为fiber、queue、update
- update里的action就是触发dispatch的时候传进来的options
- 这个数组是一个公共变量
- 然后向上查找的根fiber,并传递给重新渲染函数
视图刷新
- ensureRootIsScheduled
- 这里加了一层处理,因为我们的有时候一次会传入多个更新,那么都扔在一个queue里了,如果不处理会多次更新,没有必要,如果有更新的就退出本轮
- 也可以理解为
更新合并
function ensureRootIsScheduled(root) {
if (workInProgressRoot) return;
workInProgressRoot = root;
scheduleCallback(performConcurrentWorkOnRoot.bind(null, root));
}
- 在视图刷新前执行finishQueueingConcurrentUpdates
- 将
queue里的待更新统一放到hook自身的队列中
,和初始化element的时候一样
最后一个更新为pending,它指向第一个更新
,单向循环链表
- 然后依然是处理另外的根fiber,然后开始处理节点
- 函数组件依然会走renderWithHooks,但这次就是更新逻辑了
renderWithHooks的更新
const HooksDispatcherOnUpdate = {
useReducer: updateReducer,
};
function updateWorkInProgressHook() {
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
currentHook = current.memoizedState;
} else {
currentHook = currentHook.next;
}
const newHook = {
memoizedState: currentHook.memoizedState,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
return workInProgressHook;
}
function updateReducer(reducer) {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
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];
}
- 依次拿到旧fiber的memoizedState,里面存的是hooks队列,依次执行刚才存储的更新,通过出入的reducer函数处理后,赋值给hooks实例的memoized
- 最终返回新的[hook.memoizedState,初始的dispatch]
- 然后进行dom的构建