在看开源项目源码过程中发现了这样一段代码
setState是useState返回数组的第二个参数,可以传一个函数,react官方文档没有记录这样的用法,决定带着问题去看看useState源码
setState(value => {
const newValue = alignInRange(value + offset);
return newValue;
});
复制代码
首先看useState代码
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
if (__DEV__) {
currentHookNameInDev = 'useState';
}
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
复制代码
可见useState基于useReducer,basicStateReducer传入了useReducer的第一个参数
再看basicStateReducer代码
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
复制代码
回顾一下useReducer的用法
const [state, dispatch] = useReducer(reducer, initialState);
复制代码
dispatch是否可以用一个函数做入参呢?
看看useReducer最后一行,返回了
const dispatch = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
)));
return [workInProgressHook.memoizedState, dispatch];
复制代码
对应了useReducer返回的state和dispatch
dispatch是dispatchAction的拷贝,this指向useReducer并具有初始参数currentlyRenderingComponent和queue
再来看看dispatchAction传入一个函数会发生什么?
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<A>,
action: A, // 把action函数传入...
){
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
action, // action放在update对象中
next: null,
};
if (renderPhaseUpdates === null) { // renderPhaseUpdates看名字猜测是更新阶段的产生的更新
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); // 如果这个更新队列的不存在这个值,则是第一次更新
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next; // 找到最后一次更新
}
lastRenderPhaseUpdate.next = update; // 修改最后一次更新的next指向,指向这一次需要更新的
}
}
复制代码
所以dispatchAction函数的作用是把需要更新的内容action添加到需要更新的action的链表末尾
所以调用dispatch后会创建一个update,在所有update收集完后会调度一次React更新
更新时会执行我们的function component,就会再次执行useState,进而执行useReducer
在来看看useReducer代码(去掉了不关键代码后)
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook(); // 返回了Hook,包含memoizedState,queue,next属性,next指向了下一个将要被指向的hook
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
}
复制代码
执行useReducer后我们拿到了Hook对象,里面的queue属性保存了有哪些需要更新,依次进行更新
上面10-31行就是执行更新的代码
其中23行的action是我们调用dispatch的入参,reducer是在调用useState时传入useReducer的
也就是上面提到的,basicStateReducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
复制代码
所以我们action如果传入一个函数,就会被执行,并且入参是更新前的state
找到答案了!