最基础的useState用法
function Counter() {
var [ count, setCount ] = useState(0)
return (
<div>
<div>{count}</div>
<button onClick={() => {setCount(count + 1)}}>点击</button>
</div>
)
}
基于useState的用法,自己实现一个useState
// 把state存储在外面
var _state
function useState(initialValue) {
// 如果没有 _state,说明是第一次执行,把 initialValue 复制给它
var state = _state || initialValue
function setState(newState) {
state = newState
render()
}
return [state, setState]
}
这样就模拟实现了一个基础的useState
当我们的代码执行到了useState的时候,他到底做了什么呢?
源码路径 /packages/react-reconciler/src/ReactFiberHooks.js
function baseStateReducer<S>(state: S, action:BaicStateAction<S>):S {
return typeof action === 'function' ? action(state) : action
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
)
}
可见useState不过就是个语法糖,本质其实就是useReducer,那么useReducer具体做了什么呢?
useReducer
最开始两句代码是每个Hooks都会做的统一代码:
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
currentlyRenderingFibercreateWorkInProgressHook
这里分两种情况:第一次渲染和更新,如果workInProgressHook.queue存在则为更新,否则是第一次渲染
第一次渲染
第一次渲染主要就是初始化操作
// There is no existing queue, so this is the initial render.
if (reducer === basicStateReducer) {
// Special case for `useState`.
if (typeof initialState === 'function') {
initialState = initialState();
}
} else if (initialAction !== undefined && initialAction !== null) {
initialState = reducer(initialState, initialAction);
}
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
这里初始化initialState,并且记录在workInProgressHook.memoizedState和workInProgressHook.baseState上
然后创建queue对象
queue = workInProgeressHook.queue = {
last: null,
dispatch: null,
}
可以看到queue的结构非常简单,只有一个last指针和dispatch,dispatch是用来记录更新state的方法的,接下去我们就要创建dispatch方法了
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any)
可以看到这个dispatch就是dispatchAction绑定了对应的Fiber和queue。最后return:
return [workInProgressHook.memoizedState, dispatch];
return的结果对应了我们const [state, updateState] = useState('default')的用法
更新
分两种情况,是否是reRender,所谓reRender就是说在当前更新周期中又产生了新的更新,就继续执行这些更新直到当前渲染周期中没有更新为止
他们基本的操作是一致的,就是根据render和update.action来创建新的state,并赋值给Hook.memoizedState以及Hook.baseState.
注意这里,对于非reRender的况,我们会对每个更新判断其优先级,如果不是当前整体更新优先级内得到更新会跳过,第一个跳过的Update会变成新的baseUpadate,他记录了在之后所有的Update,即便是优先级比他高的,因为在他被执行的时候,需要保证后续的更新要在他更新之后的基础上再次执行,因此结果可能会不一样。
dispatchAction
首先看这个判断:
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
)
这其实就是判断这个更新是否是在渲染过程中产生的,currentlyRenderingFiber只有在FunctionalComponent更新的过程中才会被设置,在离开更新的时候设置为null,所以只要存在并与产生更新的Fiber相等,说明这个更新时在当前渲染中产生的,则这是一次reRender。
所有更新过程中只产生的更新记录在renderPhaseUpdates这个Map上,以每个Hook的queue为key。
对于不是更新过程中生成的更新,则直接在queue上执行操作就行了,注意在最后会发起一次scheduleWork的调度。
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we will restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
expirationTime: renderExpirationTime,
action,
next: null,
};
if (renderPhaseUpdates === null) {
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;
}
} else {
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update: Update<A> = {
expirationTime,
action,
next: null,
};
flushPassiveEffects();
// Append the update to the end of the list.
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// Still circular.
update.next = first;
}
last.next = update;
}
queue.last = update;
scheduleWork(fiber, expirationTime);
}
}