// 下面这个报错应该都见过 invariant( dispatcher !== null, "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for" + " one of the following reasons:\n" + "1. You might have mismatching versions of React and the renderer (such as React DOM)\n" + "2. You might be breaking the Rules of Hooks\n" + "3. You might have more than one copy of React in the same app\n" + "See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem." ); return dispatcher; }
exportfunction renderWithHooks<Props, SecondArg>( current: Fiber | null, workInProgress: Fiber, Component: (p: Props, arg: SecondArg) =>any, props: Props, secondArg: SecondArg, nextRenderExpirationTime: ExpirationTime ): any { //... if (__DEV__) { if (current !== null && current.memoizedState !== null) { ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV; } elseif (hookTypesDev !== null) { // This dispatcher handles an edge case where a component is updating, // but no stateful hooks have been used. // We want to match the production code behavior (which will use HooksDispatcherOnMount), // but with the extra DEV validation to ensure hooks ordering hasn't changed. // This dispatcher does that. ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV; } else { ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV; } } else { ReactCurrentDispatcher.current = current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; }
//... // 还原原有的 Dispatcher , 原有的每个都是警告函数 // We can assume the previous dispatcher is always this one, since we set it // at the beginning of the render phase and there's no re-entrancy. ReactCurrentDispatcher.current = ContextOnlyDispatcher; }
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) { // 开发下第四个参数报错,因为 hooks 没有回调. if (__DEV__) { if (typeofarguments[3] === 'function') { console.error( "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().', ); } }
if (__DEV__) { update.priority = getCurrentPriorityLevel(); }
// Append the update to the end of the list. // 把 update 对象添添加到链表 const pending = queue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update;
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'll restart // and apply the stashed updates on top of the work-in-progress hook. didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true; update.expirationTime = renderExpirationTime; } else { if ( fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork) ) { // The queue is currently empty, which means we can eagerly compute the // next state before entering the render phase. If the new state is the // same as the current state, we may be able to bail out entirely. const lastRenderedReducer = queue.lastRenderedReducer; if (lastRenderedReducer !== null) { let prevDispatcher; if (__DEV__) { prevDispatcher = ReactCurrentDispatcher.current; ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV; } try { // 当前的旧值 constcurrentState: S = (queue.lastRenderedState: any); // 新值 const eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute // it, on the update object. If the reducer hasn't changed by the // time we enter the render phase, then the eager state can be used // without calling the reducer again. update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { // Fast path. We can bail out without scheduling React to re-render. // It's still possible that we'll need to rebase this update later, // if the component re-renders for a different reason and by that // time the reducer has changed. return; } } catch (error) { // Suppress the error. It will throw again in the render phase. } finally { if (__DEV__) { ReactCurrentDispatcher.current = prevDispatcher; } } } }
scheduleUpdateOnFiber(fiber, expirationTime); }
}
这样就准备了 update 对象添加到了 Fiber 对象上,然后安排更新, 剩下的更新部分就和 class 的 this.setStae 一样了,等待调度,然后给更新
function updateReducer<S, I, A>( reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>] { // 从 Fiber 对象上读取 hook 对象, 然后克隆一个新的返回 const hook = updateWorkInProgressHook(); const queue = hook.queue; invariant( queue !== null, 'Should have a queue. This is likely a bug in React. Please file an issue.', );
queue.lastRenderedReducer = reducer;
constcurrent: Hook = (currentHook: any);
// The last rebase update that is NOT part of the base state. // 部署于基准状态的最后一个更新 let baseQueue = current.baseQueue;
// The last pending update that hasn't been processed yet. // 尚未处理的最后一个更新 const pendingQueue = queue.pending; if (pendingQueue !== null) { // We have new updates that haven't been processed yet. // We'll add them to the base queue. // 我们有新的未处理的 更新 // 我们将它添加到 队列里面 if (baseQueue !== null) { // Merge the pending queue and the base queue. // 合并 等待更新队列和基础队列 const baseFirst = baseQueue.next; const pendingFirst = pendingQueue.next; baseQueue.next = pendingFirst; pendingQueue.next = baseFirst; } if (__DEV__) { if (current.baseQueue !== baseQueue) { // Internal invariant that should never happen, but feasibly could in // the future if we implement resuming, or some form of that. console.error( 'Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.', ); } } current.baseQueue = baseQueue = pendingQueue; queue.pending = null; }
if (baseQueue !== null) { // We have a queue to process. const first = baseQueue.next; let newState = current.baseState;
let newBaseState = null; let newBaseQueueFirst = null; let newBaseQueueLast = null; let update = first; do { const updateExpirationTime = update.expirationTime; if (updateExpirationTime < renderExpirationTime) { // Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. constclone: Update<S, A> = { expirationTime: update.expirationTime, suspenseConfig: update.suspenseConfig, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; newBaseState = newState; } else { newBaseQueueLast = newBaseQueueLast.next = clone; } // Update the remaining priority in the queue. if (updateExpirationTime > currentlyRenderingFiber.expirationTime) { currentlyRenderingFiber.expirationTime = updateExpirationTime; markUnprocessedUpdateTime(updateExpirationTime); } } else { // This update does have sufficient priority.
if (newBaseQueueLast !== null) { constclone: Update<S, A> = { expirationTime: Sync, // This update is going to be committed so we never want uncommit it. suspenseConfig: update.suspenseConfig, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; newBaseQueueLast = newBaseQueueLast.next = clone; }
// Mark the event time of this update as relevant to this render pass. // TODO: This should ideally use the true event time of this update rather than // its priority which is a derived and not reverseable value. // TODO: We should skip this update if it was already committed but currently // we have no way of detecting the difference between a committed and suspended // update here. markRenderEventTimeAndConfig( updateExpirationTime, update.suspenseConfig, );
// Process this update. if (update.eagerReducer === reducer) { // If this update was processed eagerly, and its reducer matches the // current reducer, we can use the eagerly computed state. newState = ((update.eagerState: any): S); } else { const action = update.action; newState = reducer(newState, action); } } update = update.next; } while (update !== null && update !== first);
// Mark that the fiber performed work, but only if the new state is // different from the current state. if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate(); }