Best JavaScript code snippet using playwright-internal
ReactFiberHooks.new.js
Source:ReactFiberHooks.new.js
1/**2 * Copyright (c) Facebook, Inc. and its affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */9import type {10 MutableSource,11 MutableSourceGetSnapshotFn,12 MutableSourceSubscribeFn,13} from 'shared/ReactTypes';14import type {Fiber, Dispatcher} from './ReactInternalTypes';15import type {Lanes, Lane} from './ReactFiberLane';16import type {HookFlags} from './ReactHookEffectTags';17import type {ReactPriorityLevel} from './ReactInternalTypes';18import type {FiberRoot} from './ReactInternalTypes';19import type {OpaqueIDType} from './ReactFiberHostConfig';20import ReactSharedInternals from 'shared/ReactSharedInternals';21import {22 enableSchedulingProfiler,23 enableNewReconciler,24} from 'shared/ReactFeatureFlags';25import {NoMode, BlockingMode} from './ReactTypeOfMode';26import {27 NoLane,28 NoLanes,29 isSubsetOfLanes,30 mergeLanes,31 removeLanes,32 markRootEntangled,33 markRootMutableRead,34} from './ReactFiberLane';35import {readContext} from './ReactFiberNewContext.new';36import {37 Update as UpdateEffect,38 Passive as PassiveEffect,39 PassiveStatic as PassiveStaticEffect,40} from './ReactFiberFlags';41import {42 HasEffect as HookHasEffect,43 Layout as HookLayout,44 Passive as HookPassive,45} from './ReactHookEffectTags';46import {47 getWorkInProgressRoot,48 scheduleUpdateOnFiber,49 requestUpdateLane,50 requestEventTime,51 markSkippedUpdateLanes,52} from './ReactFiberWorkLoop.new';53import invariant from 'shared/invariant';54import is from 'shared/objectIs';55import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';56import {57 UserBlockingPriority,58 NormalPriority,59 runWithPriority,60 getCurrentPriorityLevel,61} from './SchedulerWithReactIntegration.new';62import {getIsHydrating} from './ReactFiberHydrationContext.new';63import {64 makeClientId,65 makeOpaqueHydratingObject,66} from './ReactFiberHostConfig';67import {68 getWorkInProgressVersion,69 markSourceAsDirty,70 setWorkInProgressVersion,71} from './ReactMutableSource.new';72import {markStateUpdateScheduled} from './SchedulingProfiler';73import { enableLog } from 'shared/ReactFeatureFlags';74const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;75type Update<S, A> = {76 lane: Lane,77 action: A,78 eagerReducer: ((S, A) => S) | null,79 eagerState: S | null,80 next: Update<S, A>,81 priority?: ReactPriorityLevel,82};83type UpdateQueue<S, A> = {84 pending: Update<S, A> | null,85 dispatch: (A => mixed) | null,86 lastRenderedReducer: ((S, A) => S) | null,87 lastRenderedState: S | null,88};89export type HookType =90 | 'useState'91 | 'useReducer'92 | 'useContext'93 | 'useRef'94 | 'useEffect'95 | 'useLayoutEffect'96 | 'useCallback'97 | 'useMemo'98 | 'useImperativeHandle'99 | 'useDebugValue'100 | 'useDeferredValue'101 | 'useTransition'102 | 'useMutableSource'103 | 'useOpaqueIdentifier';104export type Hook = {105 memoizedState: any,106 baseState: any,107 baseQueue: Update<any, any> | null,108 queue: UpdateQueue<any, any> | null,109 next: Hook | null,110};111export type Effect = {112 tag: HookFlags,113 create: () => (() => void) | void,114 destroy: (() => void) | void,115 deps: Array<mixed> | null,116 next: Effect,117};118export type FunctionComponentUpdateQueue = {lastEffect: Effect | null};119type BasicStateAction<S> = (S => S) | S;120type Dispatch<A> = A => void;121// These are set right before calling the component.122let renderLanes: Lanes = NoLanes;123// The work-in-progress fiber. I've named it differently to distinguish it from124// the work-in-progress hook.125let currentlyRenderingFiber: Fiber = (null: any);126// Hooks are stored as a linked list on the fiber's memoizedState field. The127// current hook list is the list that belongs to the current fiber. The128// work-in-progress hook list is a new list that will be added to the129// work-in-progress fiber.130let currentHook: Hook | null = null;131let workInProgressHook: Hook | null = null;132// Whether an update was scheduled at any point during the render phase. This133// does not get reset if we do another render pass; only when we're completely134// finished evaluating this component. This is an optimization so we know135// whether we need to clear render phase updates after a throw.136let didScheduleRenderPhaseUpdate: boolean = false;137// Where an update was scheduled only during the current render pass. This138// gets reset after each attempt.139// TODO: Maybe there's some way to consolidate this with140// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.141let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;142const RE_RENDER_LIMIT = 25;143function throwInvalidHookError() {144 throw new Error(145 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +146 ' one of the following reasons:\n' +147 '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +148 '2. You might be breaking the Rules of Hooks\n' +149 '3. You might have more than one copy of React in the same app\n' +150 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',151 );152}153function areHookInputsEqual(154 nextDeps: Array<mixed>,155 prevDeps: Array<mixed> | null,156) {157 if (prevDeps === null) {158 return false;159 }160 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {161 if (is(nextDeps[i], prevDeps[i])) {162 continue;163 }164 return false;165 }166 return true;167}168/** è¿åReactElement */169export function renderWithHooks<Props, SecondArg>(170 current: Fiber | null,171 workInProgress: Fiber,172 Component: (p: Props, arg: SecondArg) => any,173 props: Props,174 secondArg: SecondArg,175 nextRenderLanes: Lanes,176): any {177 178 enableLog && console.log('ReactFiberHooks.new: renderWithHooks start')179 if (!__LOG_NAMES__.length || __LOG_NAMES__.includes('renderWithHooks')) debugger180 /** 设置ä¸äºå
¨å±åé start */181 renderLanes = nextRenderLanes;182 currentlyRenderingFiber = workInProgress;183 workInProgress.memoizedState = null;184 workInProgress.updateQueue = null;185 workInProgress.lanes = NoLanes;186 /** 设置ä¸äºå
¨å±åé end */187 // The following should have already been reset188 // currentHook = null;189 // workInProgressHook = null;190 // didScheduleRenderPhaseUpdate = false;191 // TODO Warn if no hooks are used at all during mount, then some are used during update.192 // Currently we will identify the update render as a mount because memoizedState === null.193 // This is tricky because it's valid for certain types of components (e.g. React.lazy)194 // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.195 // Non-stateful hooks (e.g. context) don't get added to memoizedState,196 // so memoizedState would be null during updates and mounts.197 ReactCurrentDispatcher.current =198 current === null || current.memoizedState === null199 ? HooksDispatcherOnMount200 : HooksDispatcherOnUpdate;201 // å¨è¿éå¼å§è°ç¨å½æ°ç»ä»¶ï¼éé¢å¯è½ç¨å°hookï¼children为ReactElement202 let children = Component(props, secondArg);203 // Check if there was a render phase update204 if (didScheduleRenderPhaseUpdateDuringThisPass) {205 // Keep rendering in a loop for as long as render phase updates continue to206 // be scheduled. Use a counter to prevent infinite loops.207 let numberOfReRenders: number = 0;208 do {209 didScheduleRenderPhaseUpdateDuringThisPass = false;210 invariant(211 numberOfReRenders < RE_RENDER_LIMIT,212 'Too many re-renders. React limits the number of renders to prevent ' +213 'an infinite loop.',214 );215 numberOfReRenders += 1;216 // Start over from the beginning of the list217 currentHook = null;218 workInProgressHook = null;219 workInProgress.updateQueue = null;220 ReactCurrentDispatcher.current = HooksDispatcherOnRerender;221 children = Component(props, secondArg);222 } while (didScheduleRenderPhaseUpdateDuringThisPass);223 }224 // We can assume the previous dispatcher is always this one, since we set it225 // at the beginning of the render phase and there's no re-entrancy.226 /**227 * Component(props, secondArg)è¿è¡åï¼ReactCurrentDispatcher.currentæåContextOnlyDispatcher228 * é£ä¹å¦æåå½æ°ä¸ä½¿ç¨äºhookï¼æ¯å¦useEffect(() => {useState(0);})ï¼229 * åContextOnlyDispatcher.useStateæåthrowInvalidHookErrorï¼ä¼æåºé误230 */231 232 ReactCurrentDispatcher.current = ContextOnlyDispatcher;233 // This check uses currentHook so that it works the same in DEV and prod bundles.234 // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.235 // å°äºè¿écurrentHookä¸æ¯æåä¸ä¸ªhookï¼åæå³æ¯è¿è¡å°äºï¼æå¯è½ä¸äºhookä¸é¢æreturnè¯å¥æ»¡è¶³æ¡ä»¶236 const didRenderTooFewHooks =237 currentHook !== null && currentHook.next !== null;238 // ä¸é¢çchildren = Component(props, secondArg);è¿è¡åï¼ä¸é¢çå°±éç½®æ239 /** éç½®ä¸äºå
¨å±åé start */240 renderLanes = NoLanes;241 currentlyRenderingFiber = (null: any);242 currentHook = null;243 workInProgressHook = null;244 didScheduleRenderPhaseUpdate = false;245 /** éç½®ä¸äºå
¨å±åé end */246 invariant(247 !didRenderTooFewHooks,248 'Rendered fewer hooks than expected. This may be caused by an accidental ' +249 'early return statement.',250 );251 return children;252}253export function bailoutHooks(254 current: Fiber,255 workInProgress: Fiber,256 lanes: Lanes,257) {258 workInProgress.updateQueue = current.updateQueue;259 workInProgress.flags &= ~(PassiveEffect | UpdateEffect);260 current.lanes = removeLanes(current.lanes, lanes);261}262export function resetHooksAfterThrow(): void {263 // We can assume the previous dispatcher is always this one, since we set it264 // at the beginning of the render phase and there's no re-entrancy.265 ReactCurrentDispatcher.current = ContextOnlyDispatcher;266 if (didScheduleRenderPhaseUpdate) {267 // There were render phase updates. These are only valid for this render268 // phase, which we are now aborting. Remove the updates from the queues so269 // they do not persist to the next render. Do not remove updates from hooks270 // that weren't processed.271 //272 // Only reset the updates from the queue if it has a clone. If it does273 // not have a clone, that means it wasn't processed, and the updates were274 // scheduled before we entered the render phase.275 let hook: Hook | null = currentlyRenderingFiber.memoizedState;276 while (hook !== null) {277 const queue = hook.queue;278 if (queue !== null) {279 queue.pending = null;280 }281 hook = hook.next;282 }283 didScheduleRenderPhaseUpdate = false;284 }285 renderLanes = NoLanes;286 currentlyRenderingFiber = (null: any);287 currentHook = null;288 workInProgressHook = null;289 didScheduleRenderPhaseUpdateDuringThisPass = false;290}291function mountWorkInProgressHook(): Hook {292 const hook: Hook = {293 memoizedState: null,294 baseState: null,295 baseQueue: null,296 queue: null,297 next: null,298 };299 if (workInProgressHook === null) {300 // This is the first hook in the list301 currentlyRenderingFiber.memoizedState = workInProgressHook = hook;302 } else {303 // Append to the end of the list304 workInProgressHook = workInProgressHook.next = hook;305 }306 return workInProgressHook;307}308function updateWorkInProgressHook(): Hook {309 // This function is used both for updates and for re-renders triggered by a310 // render phase update. It assumes there is either a current hook we can311 // clone, or a work-in-progress hook from a previous render pass that we can312 // use as a base. When we reach the end of the base list, we must switch to313 // the dispatcher used for mounts.314 enableLog && console.log('updateWorkInProgressHook start')315 if (!__LOG_NAMES__.length || __LOG_NAMES__.includes('updateWorkInProgressHook')) debugger316 /**317 * currentlyRenderingFiberæåè°ç¨updateWorkInProgressHookçå½æ°ç»ä»¶å¯¹åºçworkInProgress(Fiber)318 */319 /* è¿éæ¯é对æ§hookçå¤æ start */320 let nextCurrentHook: null | Hook;321 /**322 * å¨renderWithHooksä¸ï¼åè¿å
¥è¯¥å½æ°currentHook为nullï¼323 * ä¹åä¼è°ç¨çlet children = Component(props, secondArg)ï¼å³è°ç¨å½æ°ç»ä»¶ï¼324 * 说æåè¿å
¥Componentå½æ°ç»ä»¶æ¯currentHook为nullï¼å½è°ç¨å®Component(props, secondArg)ï¼325 * currentHookä¹ä¼ç½®ä¸ºnullï¼æ»ç»ä¸å¥è¯å°±æ¯ï¼326 * å¨è°ç¨Component(props, secondArg)ååcurrentHooké½ä¸ºnull327 */328 if (currentHook === null) {329 // currentHook为nullï¼è¯´æå½æ°ç»ä»¶è¿æ¬¡æ´æ°ç¬¬ä¸æ¬¡è°ç¨updateWorkInProgressHook330 const current = currentlyRenderingFiber.alternate;331 if (current !== null) {332 // å¦æå½æ°ç»ä»¶å¯¹åºFiberæcurrentï¼åcurrent.memoizedStateæåæ´æ°åç第ä¸ä¸ªhook333 nextCurrentHook = current.memoizedState;334 } else {335 nextCurrentHook = null;336 }337 } else {338 // ä¸ä¸ºnullï¼è¯´æå½æ°ç»ä»¶å·²ç»è°ç¨äºä¸åªä¸æ¬¡updateWorkInProgressHookï¼ç´æ¥ånext339 nextCurrentHook = currentHook.next;340 }341 /* è¿éæ¯é对æ§hookçå¤æ end */342 /* è¿éæ¯é对æ°hookçå¤æ start */343 let nextWorkInProgressHook: null | Hook;344 if (workInProgressHook === null) {345 /**346 * workInProgressHook为nullï¼è¯´æå½æ°ç»ä»¶è¿æ¬¡æ´æ°ç¬¬ä¸æ¬¡è°ç¨updateWorkInProgressHook347 * è¿é注æï¼workInProgress.memoizedStateå¨è°ç¨renderWithHookså
¥å£å°±è¢«ç½®ä¸ºnulläº,348 * ä½æ¯è¿écurrentlyRenderingFiber.memoizedStateä¸ä¸å®ä¸ºnull!!!349 * åå æ¯å¦æå¨renderçæ¶ååè°ç¨äºsetStateï¼ç»ä»¶ç»§ç»æ´æ°ï¼350 * é£ä¹è¿éçcurrentlyRenderingFiber.memoizedStateå°±ä¸ä¸ºnullï¼351 * å¦åæ£å¸¸æ
åµè¿éçcurrentlyRenderingFiber.memoizedState为null352 */353 nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;354 } else {355 /**356 * ä¸ä¸ºnullï¼è¯´æå½æ°ç»ä»¶å·²ç»è°ç¨äºä¸åªä¸æ¬¡updateWorkInProgressHookï¼ç´æ¥ånext357 * æ£å¸¸æ
åµä¸è¿éworkInProgressHook.next为nullï¼358 * ä½æ¯å¦æå¨renderçæ¶åå触åäº359 */360 nextWorkInProgressHook = workInProgressHook.next;361 }362 /* è¿éæ¯é对æ°hookçå¤æ end */363 364 /**365 * æç
§ä¸é¢çåæï¼æ£å¸¸æ
åµæ们ä¸ä¼å¨renderçæ¶åè°ç¨setStateï¼366 * æ以è¿énextWorkInProgressHookä¸è¬é½ä¸ºnullï¼ä¹å°±æ¯è¯´ä¸è¬é½ä¼èµ°elseåæ¯ã367 * å¯å¦æå¨renderçæ¶åè°ç¨äºsetStateï¼é£ä¹è¿éçnextWorkInProgressHookå°±ä¸ä¸ºnull368 */369 if (nextWorkInProgressHook !== null) {370 // There's already a work-in-progress. Reuse it.371 workInProgressHook = nextWorkInProgressHook;372 nextWorkInProgressHook = workInProgressHook.next;373 currentHook = nextCurrentHook;374 } else {375 // Clone from the current hook.376 /**377 * è¿éæ¯ä¸è¬æµç¨é½ä¼èµ°çï¼å°±æ¯ä¼å¤ç¨å°±çhook378 */379 invariant(380 nextCurrentHook !== null,381 'Rendered more hooks than during the previous render.',382 );383 currentHook = nextCurrentHook;384 const newHook: Hook = {385 memoizedState: currentHook.memoizedState,386 baseState: currentHook.baseState,387 baseQueue: currentHook.baseQueue,388 queue: currentHook.queue,389 next: null,390 };391 /**392 * å¦æworkInProgressHook为nullï¼æå³çæ¯ç¬¬ä¸æ¬¡è°ç¨updateWorkInProgressHook393 */394 if (workInProgressHook === null) {395 // This is the first hook in the list.396 // memoizedStateæå第ä¸ä¸ªhook397 currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;398 } else {399 // Append to the end of the list.400 // å¦ååä¸ä¸ªhookçnextæåæ°çæçhookï¼è¿æ¥åworkInProgressHook移å¨å°newHook401 workInProgressHook = workInProgressHook.next = newHook;402 }403 }404 return workInProgressHook;405}406function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {407 return {408 lastEffect: null,409 };410}411function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {412 // $FlowFixMe: Flow doesn't like mixed types413 return typeof action === 'function' ? action(state) : action;414}415function mountReducer<S, I, A>(416 reducer: (S, A) => S,417 initialArg: I,418 init?: I => S,419): [S, Dispatch<A>] {420 const hook = mountWorkInProgressHook();421 let initialState;422 if (init !== undefined) {423 initialState = init(initialArg);424 } else {425 initialState = ((initialArg: any): S);426 }427 hook.memoizedState = hook.baseState = initialState;428 const queue = (hook.queue = {429 pending: null,430 dispatch: null,431 lastRenderedReducer: reducer,432 lastRenderedState: (initialState: any),433 });434 const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(435 null,436 currentlyRenderingFiber,437 queue,438 ): any));439 return [hook.memoizedState, dispatch];440}441function updateReducer<S, I, A>(442 reducer: (S, A) => S,443 initialArg: I,444 init?: I => S,445): [S, Dispatch<A>] {446 const hook = updateWorkInProgressHook();447 const queue = hook.queue;448 invariant(449 queue !== null,450 'Should have a queue. This is likely a bug in React. Please file an issue.',451 );452 // æ¯æ¬¡è°ç¨é½ä¼æqueue.lastRenderedReduceræåææ°çreducer453 queue.lastRenderedReducer = reducer;454 const current: Hook = (currentHook: any);455 // The last rebase update that is NOT part of the base state.456 let baseQueue = current.baseQueue;457 // The last pending update that hasn't been processed yet.458 const pendingQueue = queue.pending;459 // è¿é对产ççupdateè¿è¡å¤ä»½ï¼å¤ä»½å°current.baseQueueä¸ï¼å 为产ççæ´æ°å¯è½å 为ä¼å
级460 // ä¸è¶³å被跳è¿ï¼ä½è·³è¿å½è·³è¿ï¼ä¹åè¿æ¯è¦æ¢å¤éåï¼ä¸è¦ä¿è¯update产çç顺åºï¼è¿å°±æ¯è¦å¤ä»½çåå 461 if (pendingQueue !== null) {462 // pendingQueueä¸ä¸ºç©ºåæå³çæupdate463 // We have new updates that haven't been processed yet.464 // We'll add them to the base queue.465 if (baseQueue !== null) {466 // ç±äºè¿äºæ°çupdateè¿æªå¤çï¼åæåå°currentçbaseQueueä¸467 // Merge the pending queue and the base queue.468 const baseFirst = baseQueue.next;469 const pendingFirst = pendingQueue.next;470 baseQueue.next = pendingFirst;471 pendingQueue.next = baseFirst;472 }473 // ä¸é¢æ¼æ¥åï¼å°current.baseQueueæåpendingQueue474 current.baseQueue = baseQueue = pendingQueue;475 // ç¶åwipHook.queue.pendingå°±å¯ä»¥æ¸
空äºï¼å 为ä¸é¢å¤ä»½äº476 queue.pending = null;477 }478 if (baseQueue !== null) {479 // å¤çbaseQueue480 // We have a queue to process.481 // 第ä¸ä¸ªupdate482 const first = baseQueue.next;483 let newState = current.baseState;484 // 以ä¸ä¸ä¸ªnewBaseä»
å¨æupdate被跳è¿æ¶ï¼ææå¼485 let newBaseState = null;486 // newBaseQueueFirstånewBaseQueueLastç»ænewBaseQueue487 let newBaseQueueFirst = null;488 let newBaseQueueLast = null;489 let update = first;490 do {491 // è·åæ´æ°ä¼å
级492 const updateLane = update.lane;493 if (!isSubsetOfLanes(renderLanes, updateLane)) {494 // å¦æä¸å¨æ¸²æä¼å
级éï¼åè¦è·³è¿495 // Priority is insufficient. Skip this update. If this is the first496 // skipped update, the previous update/state is the new base497 // update/state.498 const clone: Update<S, A> = {499 lane: updateLane,500 action: update.action,501 eagerReducer: update.eagerReducer,502 eagerState: update.eagerState,503 next: (null: any),504 };505 if (newBaseQueueLast === null) {506 // newBaseQueueLast为空代表è¿ä¸ªupdateæ¯ç¬¬ä¸ä¸ªè¢«è·³è¿çï¼507 // é£ä¹ä½ä¸ºnewBaseQueueLastç第ä¸ä¸ª508 newBaseQueueFirst = newBaseQueueLast = clone;509 // newBaseStateèµå¼ä¸ºè¢«è·³è¿updateçåä¸ä¸ªupdate产ççnewState510 newBaseState = newState;511 } else {512 // å¦åæ¼æ¥ånewBaseQueueåé¢513 newBaseQueueLast = newBaseQueueLast.next = clone;514 }515 // Update the remaining priority in the queue.516 // TODO: Don't need to accumulate this. Instead, we can remove517 // renderLanes from the original lanes.518 currentlyRenderingFiber.lanes = mergeLanes(519 currentlyRenderingFiber.lanes,520 updateLane,521 );522 // å 为被跳è¿ï¼æ以è¿éè¦æ 记被跳è¿çæ´æ°ä¼å
级523 markSkippedUpdateLanes(updateLane);524 } else {525 // This update does have sufficient priority.526 if (newBaseQueueLast !== null) {527 // è¿énewBaseQueueLastä¸ä¸ºç©ºï¼åæå³çä¹åå·²ç»æä¼å
级ä¸è¶³çupdate被跳è¿äº528 const clone: Update<S, A> = {529 // This update is going to be committed so we never want uncommit530 // it. Using NoLane works because 0 is a subset of all bitmasks, so531 // this will never be skipped by the check above.532 lane: NoLane,533 action: update.action,534 eagerReducer: update.eagerReducer,535 eagerState: update.eagerState,536 next: (null: any),537 };538 // 第ä¸ä¸ªè¢«è·³è¿çupdateå°ä¹åçupdateé½æ¾å
¥newBaseQueue539 newBaseQueueLast = newBaseQueueLast.next = clone;540 }541 // Process this update.542 if (update.eagerReducer === reducer) {543 /**å¨dispatchActionéé¢å·²ç»è®¡ç®äºeagerStateï¼é£ä¹å°±ä¸ç¨å计ç®ä¸æ¬¡äºï¼544 * è¿ä¹æ¯ä¸ç§æ§è½ä¼åï¼å 为æå¯è½è®¡ç®çstateæ¯è¾èæ¶545 * dispatchActionéé¢ç¸å
³ä»£ç å¦ä¸ï¼546 * update.eagerReducer = lastRenderedReducer;547 * update.eagerState = eagerState;548 */549 // If this update was processed eagerly, and its reducer matches the550 // current reducer, we can use the eagerly computed state.551 newState = ((update.eagerState: any): S);552 } else {553 // å¦å计ç®newState554 const action = update.action;555 newState = reducer(newState, action);556 }557 }558 // 移å¨å°ä¸ä¸ä¸ªupdate559 update = update.next;560 } while (update !== null && update !== first);561 if (newBaseQueueLast === null) {562 // newBaseQueueLast为空æå³ç没æupdate被跳è¿ï¼é£ä¹newBaseStateå°±çäºnewState563 newBaseState = newState;564 } else {565 // ä¸ä¸ºç©ºï¼åæ¼æ¥ä¸ºååç¯å½¢é¾è¡¨566 newBaseQueueLast.next = (newBaseQueueFirst: any);567 }568 // Mark that the fiber performed work, but only if the new state is569 // different from the current state.570 // æç»è®¡ç®åºçnewStateå¦æåä¸æ¬¡çstateä¸åï¼åè¦æ è®°didReceiveUpdate = true571 if (!is(newState, hook.memoizedState)) {572 markWorkInProgressReceivedUpdate();573 }574 hook.memoizedState = newState;575 hook.baseState = newBaseState;576 hook.baseQueue = newBaseQueueLast;577 queue.lastRenderedState = newState;578 }579 const dispatch: Dispatch<A> = (queue.dispatch: any);580 return [hook.memoizedState, dispatch];581}582function rerenderReducer<S, I, A>(583 reducer: (S, A) => S,584 initialArg: I,585 init?: I => S,586): [S, Dispatch<A>] {587 const hook = updateWorkInProgressHook();588 const queue = hook.queue;589 invariant(590 queue !== null,591 'Should have a queue. This is likely a bug in React. Please file an issue.',592 );593 queue.lastRenderedReducer = reducer;594 // This is a re-render. Apply the new render phase updates to the previous595 // work-in-progress hook.596 const dispatch: Dispatch<A> = (queue.dispatch: any);597 const lastRenderPhaseUpdate = queue.pending;598 let newState = hook.memoizedState;599 if (lastRenderPhaseUpdate !== null) {600 // The queue doesn't persist past this render pass.601 queue.pending = null;602 const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;603 let update = firstRenderPhaseUpdate;604 do {605 // Process this render phase update. We don't have to check the606 // priority because it will always be the same as the current607 // render's.608 const action = update.action;609 newState = reducer(newState, action);610 update = update.next;611 } while (update !== firstRenderPhaseUpdate);612 // Mark that the fiber performed work, but only if the new state is613 // different from the current state.614 if (!is(newState, hook.memoizedState)) {615 markWorkInProgressReceivedUpdate();616 }617 hook.memoizedState = newState;618 // Don't persist the state accumulated from the render phase updates to619 // the base state unless the queue is empty.620 // TODO: Not sure if this is the desired semantics, but it's what we621 // do for gDSFP. I can't remember why.622 if (hook.baseQueue === null) {623 hook.baseState = newState;624 }625 queue.lastRenderedState = newState;626 }627 return [newState, dispatch];628}629type MutableSourceMemoizedState<Source, Snapshot> = {630 refs: {631 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,632 setSnapshot: Snapshot => void,633 },634 source: MutableSource<any>,635 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,636};637function readFromUnsubcribedMutableSource<Source, Snapshot>(638 root: FiberRoot,639 source: MutableSource<Source>,640 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,641): Snapshot {642 const getVersion = source._getVersion;643 const version = getVersion(source._source);644 // Is it safe for this component to read from this source during the current render?645 let isSafeToReadFromSource = false;646 // Check the version first.647 // If this render has already been started with a specific version,648 // we can use it alone to determine if we can safely read from the source.649 const currentRenderVersion = getWorkInProgressVersion(source);650 if (currentRenderVersion !== null) {651 // It's safe to read if the store hasn't been mutated since the last time652 // we read something.653 isSafeToReadFromSource = currentRenderVersion === version;654 } else {655 // If there's no version, then this is the first time we've read from the656 // source during the current render pass, so we need to do a bit more work.657 // What we need to determine is if there are any hooks that already658 // subscribed to the source, and if so, whether there are any pending659 // mutations that haven't been synchronized yet.660 //661 // If there are no pending mutations, then `root.mutableReadLanes` will be662 // empty, and we know we can safely read.663 //664 // If there *are* pending mutations, we may still be able to safely read665 // if the currently rendering lanes are inclusive of the pending mutation666 // lanes, since that guarantees that the value we're about to read from667 // the source is consistent with the values that we read during the most668 // recent mutation.669 isSafeToReadFromSource = isSubsetOfLanes(670 renderLanes,671 root.mutableReadLanes,672 );673 if (isSafeToReadFromSource) {674 // If it's safe to read from this source during the current render,675 // store the version in case other components read from it.676 // A changed version number will let those components know to throw and restart the render.677 setWorkInProgressVersion(source, version);678 }679 }680 if (isSafeToReadFromSource) {681 const snapshot = getSnapshot(source._source);682 return snapshot;683 } else {684 // This handles the special case of a mutable source being shared between renderers.685 // In that case, if the source is mutated between the first and second renderer,686 // The second renderer don't know that it needs to reset the WIP version during unwind,687 // (because the hook only marks sources as dirty if it's written to their WIP version).688 // That would cause this tear check to throw again and eventually be visible to the user.689 // We can avoid this infinite loop by explicitly marking the source as dirty.690 //691 // This can lead to tearing in the first renderer when it resumes,692 // but there's nothing we can do about that (short of throwing here and refusing to continue the render).693 markSourceAsDirty(source);694 invariant(695 false,696 'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',697 );698 }699}700function useMutableSource<Source, Snapshot>(701 hook: Hook,702 source: MutableSource<Source>,703 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,704 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,705): Snapshot {706 const root = ((getWorkInProgressRoot(): any): FiberRoot);707 invariant(708 root !== null,709 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',710 );711 const getVersion = source._getVersion;712 const version = getVersion(source._source);713 const dispatcher = ReactCurrentDispatcher.current;714 // eslint-disable-next-line prefer-const715 let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>716 readFromUnsubcribedMutableSource(root, source, getSnapshot),717 );718 let snapshot = currentSnapshot;719 // Grab a handle to the state hook as well.720 // We use it to clear the pending update queue if we have a new source.721 const stateHook = ((workInProgressHook: any): Hook);722 const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<723 Source,724 Snapshot,725 >);726 const refs = memoizedState.refs;727 const prevGetSnapshot = refs.getSnapshot;728 const prevSource = memoizedState.source;729 const prevSubscribe = memoizedState.subscribe;730 const fiber = currentlyRenderingFiber;731 hook.memoizedState = ({732 refs,733 source,734 subscribe,735 }: MutableSourceMemoizedState<Source, Snapshot>);736 // Sync the values needed by our subscription handler after each commit.737 dispatcher.useEffect(() => {738 refs.getSnapshot = getSnapshot;739 // Normally the dispatch function for a state hook never changes,740 // but this hook recreates the queue in certain cases to avoid updates from stale sources.741 // handleChange() below needs to reference the dispatch function without re-subscribing,742 // so we use a ref to ensure that it always has the latest version.743 refs.setSnapshot = setSnapshot;744 // Check for a possible change between when we last rendered now.745 const maybeNewVersion = getVersion(source._source);746 if (!is(version, maybeNewVersion)) {747 const maybeNewSnapshot = getSnapshot(source._source);748 if (!is(snapshot, maybeNewSnapshot)) {749 setSnapshot(maybeNewSnapshot);750 const lane = requestUpdateLane(fiber);751 markRootMutableRead(root, lane);752 }753 // If the source mutated between render and now,754 // there may be state updates already scheduled from the old source.755 // Entangle the updates so that they render in the same batch.756 markRootEntangled(root, root.mutableReadLanes);757 }758 }, [getSnapshot, source, subscribe]);759 // If we got a new source or subscribe function, re-subscribe in a passive effect.760 dispatcher.useEffect(() => {761 const handleChange = () => {762 const latestGetSnapshot = refs.getSnapshot;763 const latestSetSnapshot = refs.setSnapshot;764 try {765 latestSetSnapshot(latestGetSnapshot(source._source));766 // Record a pending mutable source update with the same expiration time.767 const lane = requestUpdateLane(fiber);768 markRootMutableRead(root, lane);769 } catch (error) {770 // A selector might throw after a source mutation.771 // e.g. it might try to read from a part of the store that no longer exists.772 // In this case we should still schedule an update with React.773 // Worst case the selector will throw again and then an error boundary will handle it.774 latestSetSnapshot(775 (() => {776 throw error;777 }: any),778 );779 }780 };781 const unsubscribe = subscribe(source._source, handleChange);782 return unsubscribe;783 }, [source, subscribe]);784 // If any of the inputs to useMutableSource change, reading is potentially unsafe.785 //786 // If either the source or the subscription have changed we can't can't trust the update queue.787 // Maybe the source changed in a way that the old subscription ignored but the new one depends on.788 //789 // If the getSnapshot function changed, we also shouldn't rely on the update queue.790 // It's possible that the underlying source was mutated between the when the last "change" event fired,791 // and when the current render (with the new getSnapshot function) is processed.792 //793 // In both cases, we need to throw away pending updates (since they are no longer relevant)794 // and treat reading from the source as we do in the mount case.795 if (796 !is(prevGetSnapshot, getSnapshot) ||797 !is(prevSource, source) ||798 !is(prevSubscribe, subscribe)799 ) {800 // Create a new queue and setState method,801 // So if there are interleaved updates, they get pushed to the older queue.802 // When this becomes current, the previous queue and dispatch method will be discarded,803 // including any interleaving updates that occur.804 const newQueue = {805 pending: null,806 dispatch: null,807 lastRenderedReducer: basicStateReducer,808 lastRenderedState: snapshot,809 };810 newQueue.dispatch = setSnapshot = (dispatchAction.bind(811 null,812 currentlyRenderingFiber,813 newQueue,814 ): any);815 stateHook.queue = newQueue;816 stateHook.baseQueue = null;817 snapshot = readFromUnsubcribedMutableSource(root, source, getSnapshot);818 stateHook.memoizedState = stateHook.baseState = snapshot;819 }820 return snapshot;821}822function mountMutableSource<Source, Snapshot>(823 source: MutableSource<Source>,824 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,825 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,826): Snapshot {827 const hook = mountWorkInProgressHook();828 hook.memoizedState = ({829 refs: {830 getSnapshot,831 setSnapshot: (null: any),832 },833 source,834 subscribe,835 }: MutableSourceMemoizedState<Source, Snapshot>);836 return useMutableSource(hook, source, getSnapshot, subscribe);837}838function updateMutableSource<Source, Snapshot>(839 source: MutableSource<Source>,840 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,841 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,842): Snapshot {843 const hook = updateWorkInProgressHook();844 return useMutableSource(hook, source, getSnapshot, subscribe);845}846function mountState<S>(847 initialState: (() => S) | S,848): [S, Dispatch<BasicStateAction<S>>] {849 enableLog && console.log('mountState start')850 if (!__LOG_NAMES__.length || __LOG_NAMES__.includes('mountState')) debugger851 const hook = mountWorkInProgressHook();852 if (typeof initialState === 'function') {853 // $FlowFixMe: Flow doesn't like mixed types854 initialState = initialState();855 }856 hook.memoizedState = hook.baseState = initialState;857 const queue = (hook.queue = {858 pending: null,859 dispatch: null,860 lastRenderedReducer: basicStateReducer,861 lastRenderedState: (initialState: any),862 });863 const dispatch: Dispatch<864 BasicStateAction<S>,865 > = (queue.dispatch = (dispatchAction.bind(866 null,867 currentlyRenderingFiber,868 queue,869 ): any));870 return [hook.memoizedState, dispatch];871}872function updateState<S>(873 initialState: (() => S) | S,874): [S, Dispatch<BasicStateAction<S>>] {875 return updateReducer(basicStateReducer, initialState);876}877function rerenderState<S>(878 initialState: (() => S) | S,879): [S, Dispatch<BasicStateAction<S>>] {880 return rerenderReducer(basicStateReducer, initialState);881}882/** å建ä¸ä¸ªeffectï¼è¿æ¥å°fiber.updateQueueæ«å°¾ä¸ï¼è¿å该effect */883function pushEffect(tag, create, destroy, deps) {884 const effect: Effect = {885 tag,886 create,887 destroy,888 deps,889 // Circular890 next: null,891 };892 // useEffectå建çeffecté½æ¾å¨fiber.updateQueueä¸893 let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);894 // 以ä¸æ¯è¿æ¥æååç¯å½¢é¾è¡¨895 if (componentUpdateQueue === null) {896 componentUpdateQueue = createFunctionComponentUpdateQueue();897 currentlyRenderingFiber.updateQueue = componentUpdateQueue;898 componentUpdateQueue.lastEffect = effect.next = effect;899 } else {900 const lastEffect = componentUpdateQueue.lastEffect;901 if (lastEffect === null) {902 componentUpdateQueue.lastEffect = effect.next = effect;903 } else {904 const firstEffect = lastEffect.next;905 lastEffect.next = effect;906 effect.next = firstEffect;907 componentUpdateQueue.lastEffect = effect;908 }909 }910 return effect;911}912function mountRef<T>(initialValue: T): {current: T} {913 const hook = mountWorkInProgressHook();914 const ref = {current: initialValue};915 hook.memoizedState = ref;916 return ref;917}918function updateRef<T>(initialValue: T): {current: T} {919 const hook = updateWorkInProgressHook();920 return hook.memoizedState;921}922function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {923 const hook = mountWorkInProgressHook();924 const nextDeps = deps === undefined ? null : deps;925 currentlyRenderingFiber.flags |= fiberFlags;926 hook.memoizedState = pushEffect(927 HookHasEffect | hookFlags,928 create,929 undefined,930 nextDeps,931 );932}933function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {934 const hook = updateWorkInProgressHook();935 const nextDeps = deps === undefined ? null : deps;936 let destroy = undefined;937 if (currentHook !== null) {938 const prevEffect = currentHook.memoizedState;939 destroy = prevEffect.destroy;940 if (nextDeps !== null) {941 const prevDeps = prevEffect.deps;942 // å¦æåådepsç¸åï¼åä¸èµ°ä¸é¢ï¼è¿æ¯å¤æä¾èµæ¯å¦ååçå
³é®943 if (areHookInputsEqual(nextDeps, prevDeps)) {944 // ä¸åçè¯ï¼æ³¨æè¿éçtagä¸å«HookHasEffect945 pushEffect(hookFlags, create, destroy, nextDeps);946 return;947 }948 }949 }950 currentlyRenderingFiber.flags |= fiberFlags;951 // è¿éååä¾èµååäºï¼æ以è¦å ä¸HookHasEffect952 hook.memoizedState = pushEffect(953 HookHasEffect | hookFlags,954 create,955 destroy,956 nextDeps,957 );958}959function mountEffect(960 create: () => (() => void) | void,961 deps: Array<mixed> | void | null,962): void {963 return mountEffectImpl(964 PassiveEffect | PassiveStaticEffect,965 HookPassive,966 create,967 deps,968 );969}970function updateEffect(971 create: () => (() => void) | void,972 deps: Array<mixed> | void | null,973): void {974 return updateEffectImpl(PassiveEffect, HookPassive, create, deps);975}976function mountLayoutEffect(977 create: () => (() => void) | void,978 deps: Array<mixed> | void | null,979): void {980 return mountEffectImpl(UpdateEffect, HookLayout, create, deps);981}982function updateLayoutEffect(983 create: () => (() => void) | void,984 deps: Array<mixed> | void | null,985): void {986 return updateEffectImpl(UpdateEffect, HookLayout, create, deps);987}988function imperativeHandleEffect<T>(989 create: () => T,990 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,991) {992 if (typeof ref === 'function') {993 const refCallback = ref;994 const inst = create();995 refCallback(inst);996 return () => {997 refCallback(null);998 };999 } else if (ref !== null && ref !== undefined) {1000 const refObject = ref;1001 const inst = create();1002 refObject.current = inst;1003 return () => {1004 refObject.current = null;1005 };1006 }1007}1008function mountImperativeHandle<T>(1009 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,1010 create: () => T,1011 deps: Array<mixed> | void | null,1012): void {1013 // TODO: If deps are provided, should we skip comparing the ref itself?1014 const effectDeps =1015 deps !== null && deps !== undefined ? deps.concat([ref]) : null;1016 return mountEffectImpl(1017 UpdateEffect,1018 HookLayout,1019 imperativeHandleEffect.bind(null, create, ref),1020 effectDeps,1021 );1022}1023function updateImperativeHandle<T>(1024 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,1025 create: () => T,1026 deps: Array<mixed> | void | null,1027): void {1028 // TODO: If deps are provided, should we skip comparing the ref itself?1029 const effectDeps =1030 deps !== null && deps !== undefined ? deps.concat([ref]) : null;1031 return updateEffectImpl(1032 UpdateEffect,1033 HookLayout,1034 imperativeHandleEffect.bind(null, create, ref),1035 effectDeps,1036 );1037}1038function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {1039 // This hook is normally a no-op.1040 // The react-debug-hooks package injects its own implementation1041 // so that e.g. DevTools can display custom hook values.1042}1043const updateDebugValue = mountDebugValue;1044function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {1045 const hook = mountWorkInProgressHook();1046 const nextDeps = deps === undefined ? null : deps;1047 hook.memoizedState = [callback, nextDeps];1048 return callback;1049}1050function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {1051 const hook = updateWorkInProgressHook();1052 const nextDeps = deps === undefined ? null : deps;1053 const prevState = hook.memoizedState;1054 if (prevState !== null) {1055 if (nextDeps !== null) {1056 const prevDeps: Array<mixed> | null = prevState[1];1057 if (areHookInputsEqual(nextDeps, prevDeps)) {1058 return prevState[0];1059 }1060 }1061 }1062 hook.memoizedState = [callback, nextDeps];1063 return callback;1064}1065function mountMemo<T>(1066 nextCreate: () => T,1067 deps: Array<mixed> | void | null,1068): T {1069 const hook = mountWorkInProgressHook();1070 const nextDeps = deps === undefined ? null : deps;1071 const nextValue = nextCreate();1072 hook.memoizedState = [nextValue, nextDeps];1073 return nextValue;1074}1075function updateMemo<T>(1076 nextCreate: () => T,1077 deps: Array<mixed> | void | null,1078): T {1079 const hook = updateWorkInProgressHook();1080 const nextDeps = deps === undefined ? null : deps;1081 const prevState = hook.memoizedState;1082 if (prevState !== null) {1083 // Assume these are defined. If they're not, areHookInputsEqual will warn.1084 if (nextDeps !== null) {1085 const prevDeps: Array<mixed> | null = prevState[1];1086 if (areHookInputsEqual(nextDeps, prevDeps)) {1087 return prevState[0];1088 }1089 }1090 }1091 const nextValue = nextCreate();1092 hook.memoizedState = [nextValue, nextDeps];1093 return nextValue;1094}1095function mountDeferredValue<T>(value: T): T {1096 const [prevValue, setValue] = mountState(value);1097 mountEffect(() => {1098 const prevTransition = ReactCurrentBatchConfig.transition;1099 ReactCurrentBatchConfig.transition = 1;1100 try {1101 setValue(value);1102 } finally {1103 ReactCurrentBatchConfig.transition = prevTransition;1104 }1105 }, [value]);1106 return prevValue;1107}1108function updateDeferredValue<T>(value: T): T {1109 const [prevValue, setValue] = updateState(value);1110 updateEffect(() => {1111 const prevTransition = ReactCurrentBatchConfig.transition;1112 ReactCurrentBatchConfig.transition = 1;1113 try {1114 setValue(value);1115 } finally {1116 ReactCurrentBatchConfig.transition = prevTransition;1117 }1118 }, [value]);1119 return prevValue;1120}1121function rerenderDeferredValue<T>(value: T): T {1122 const [prevValue, setValue] = rerenderState(value);1123 updateEffect(() => {1124 const prevTransition = ReactCurrentBatchConfig.transition;1125 ReactCurrentBatchConfig.transition = 1;1126 try {1127 setValue(value);1128 } finally {1129 ReactCurrentBatchConfig.transition = prevTransition;1130 }1131 }, [value]);1132 return prevValue;1133}1134function startTransition(setPending, callback) {1135 const priorityLevel = getCurrentPriorityLevel();1136 runWithPriority(1137 priorityLevel < UserBlockingPriority1138 ? UserBlockingPriority1139 : priorityLevel,1140 () => {1141 setPending(true);1142 },1143 );1144 runWithPriority(1145 priorityLevel > NormalPriority ? NormalPriority : priorityLevel,1146 () => {1147 const prevTransition = ReactCurrentBatchConfig.transition;1148 ReactCurrentBatchConfig.transition = 1;1149 try {1150 setPending(false);1151 callback();1152 } finally {1153 ReactCurrentBatchConfig.transition = prevTransition;1154 }1155 },1156 );1157}1158function mountTransition(): [(() => void) => void, boolean] {1159 const [isPending, setPending] = mountState(false);1160 // The `start` method can be stored on a ref, since `setPending`1161 // never changes.1162 const start = startTransition.bind(null, setPending);1163 mountRef(start);1164 return [start, isPending];1165}1166function updateTransition(): [(() => void) => void, boolean] {1167 const [isPending] = updateState(false);1168 const startRef = updateRef();1169 const start: (() => void) => void = (startRef.current: any);1170 return [start, isPending];1171}1172function rerenderTransition(): [(() => void) => void, boolean] {1173 const [isPending] = rerenderState(false);1174 const startRef = updateRef();1175 const start: (() => void) => void = (startRef.current: any);1176 return [start, isPending];1177}1178function warnOnOpaqueIdentifierAccessInDEV(fiber) {1179}1180function mountOpaqueIdentifier(): OpaqueIDType | void {1181 const makeId = makeClientId;1182 if (getIsHydrating()) {1183 let didUpgrade = false;1184 const fiber = currentlyRenderingFiber;1185 const readValue = () => {1186 if (!didUpgrade) {1187 // Only upgrade once. This works even inside the render phase because1188 // the update is added to a shared queue, which outlasts the1189 // in-progress render.1190 didUpgrade = true;1191 setId(makeId());1192 }1193 invariant(1194 false,1195 'The object passed back from useOpaqueIdentifier is meant to be ' +1196 'passed through to attributes only. Do not read the value directly.',1197 );1198 };1199 const id = makeOpaqueHydratingObject(readValue);1200 const setId = mountState(id)[1];1201 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {1202 currentlyRenderingFiber.flags |= PassiveEffect | PassiveStaticEffect;1203 pushEffect(1204 HookHasEffect | HookPassive,1205 () => {1206 setId(makeId());1207 },1208 undefined,1209 null,1210 );1211 }1212 return id;1213 } else {1214 const id = makeId();1215 mountState(id);1216 return id;1217 }1218}1219function updateOpaqueIdentifier(): OpaqueIDType | void {1220 const id = updateState(undefined)[0];1221 return id;1222}1223function rerenderOpaqueIdentifier(): OpaqueIDType | void {1224 const id = rerenderState(undefined)[0];1225 return id;1226}1227function dispatchAction<S, A>(1228 fiber: Fiber,1229 queue: UpdateQueue<S, A>,1230 action: A,1231) {1232 // è·åç¹å»å¼å§æ¶é´1233 const eventTime = requestEventTime();1234 enableLog && console.log('dispatchAction start')1235 if (!__LOG_NAMES__.length || __LOG_NAMES__.includes('dispatchAction')) debugger1236 1237 // è·åæ´æ°ä¼å
级1238 const lane = requestUpdateLane(fiber);1239 // å建ä¸ä¸ªupdate1240 const update: Update<S, A> = {1241 lane,1242 action,1243 eagerReducer: null,1244 eagerState: null,1245 next: (null: any),1246 };1247 // updateæ¼æ¥æç¯å½¢ååé¾è¡¨1248 // Append the update to the end of the list.1249 const pending = queue.pending;1250 if (pending === null) {1251 // This is the first update. Create a circular list.1252 update.next = update;1253 } else {1254 update.next = pending.next;1255 pending.next = update;1256 }1257 queue.pending = update;1258 const alternate = fiber.alternate;1259 if (1260 fiber === currentlyRenderingFiber ||1261 (alternate !== null && alternate === currentlyRenderingFiber)1262 ) {1263 /**1264 * å 为åªæå¨renderçæ¶å触åæ´æ°ï¼ä¸é¢çfiberæfiber.alternateæçäºcurrentlyRenderingFiber,1265 * å°didScheduleRenderPhaseUpdate设为true1266 */1267 // This is a render phase update. Stash it in a lazily-created map of1268 // queue -> linked list of updates. After this render pass, we'll restart1269 // and apply the stashed updates on top of the work-in-progress hook.1270 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;1271 } else {1272 if (1273 fiber.lanes === NoLanes &&1274 (alternate === null || alternate.lanes === NoLanes)1275 ) {1276 // fiber.lanes === NoLanesæå³çfiberä¸ä¸åå¨updateï¼1277 // é£ä¹ä¸é¢çupdateå°±æ¯queue.pending第ä¸ä¸ªupdate1278 // The queue is currently empty, which means we can eagerly compute the1279 // next state before entering the render phase. If the new state is the1280 // same as the current state, we may be able to bail out entirely.1281 /** 以ä¸æ¯æ§è½ä¼åé¨åï¼å
·ä½å¨if (is(eagerState, currentState))éé¢å¤ææ¯å¦éè¦å¼å¯ä¸æ¬¡è°åº¦ */1282 const lastRenderedReducer = queue.lastRenderedReducer;1283 if (lastRenderedReducer !== null) {1284 try {1285 const currentState: S = (queue.lastRenderedState: any);1286 const eagerState = lastRenderedReducer(currentState, action);1287 // Stash the eagerly computed state, and the reducer used to compute1288 // it, on the update object. If the reducer hasn't changed by the1289 // time we enter the render phase, then the eager state can be used1290 // without calling the reducer again.1291 // æåå°updateä¸,å¦æå¨renderé¶æ®µupdateReducerä¸å¤æå°reducer==update.eagerReducer,1292 // åå¯ä»¥ç´æ¥ä½¿ç¨æ éå次计ç®ï¼ä¼åæ§è½1293 update.eagerReducer = lastRenderedReducer;1294 update.eagerState = eagerState;1295 // å¦æ计ç®åºçstateä¸è¯¥hookä¹åä¿åçstateä¸è´ï¼é£ä¹å®å
¨ä¸éè¦å¼å¯ä¸æ¬¡è°åº¦1296 if (is(eagerState, currentState)) {1297 // Fast path. We can bail out without scheduling React to re-render.1298 // It's still possible that we'll need to rebase this update later,1299 // if the component re-renders for a different reason and by that1300 // time the reducer has changed.1301 return;1302 }1303 } catch (error) {1304 // Suppress the error. It will throw again in the render phase.1305 }1306 }1307 }1308 // 模æReactå¼å§è°åº¦æ´æ°1309 scheduleUpdateOnFiber(fiber, lane, eventTime);1310 enableLog && console.log('dispatchAction end')1311 }1312 if (enableSchedulingProfiler) {1313 markStateUpdateScheduled(fiber, lane);1314 }1315}1316export const ContextOnlyDispatcher: Dispatcher = {1317 readContext,1318 useCallback: throwInvalidHookError,1319 useContext: throwInvalidHookError,1320 useEffect: throwInvalidHookError,1321 useImperativeHandle: throwInvalidHookError,1322 useLayoutEffect: throwInvalidHookError,1323 useMemo: throwInvalidHookError,1324 useReducer: throwInvalidHookError,1325 useRef: throwInvalidHookError,1326 useState: throwInvalidHookError,1327 useDebugValue: throwInvalidHookError,1328 useDeferredValue: throwInvalidHookError,1329 useTransition: throwInvalidHookError,1330 useMutableSource: throwInvalidHookError,1331 useOpaqueIdentifier: throwInvalidHookError,1332 unstable_isNewReconciler: enableNewReconciler,1333};1334const HooksDispatcherOnMount: Dispatcher = {1335 readContext,1336 useCallback: mountCallback,1337 useContext: readContext,1338 useEffect: mountEffect,1339 useImperativeHandle: mountImperativeHandle,1340 useLayoutEffect: mountLayoutEffect,1341 useMemo: mountMemo,1342 useReducer: mountReducer,1343 useRef: mountRef,1344 useState: mountState,1345 useDebugValue: mountDebugValue,1346 useDeferredValue: mountDeferredValue,1347 useTransition: mountTransition,1348 useMutableSource: mountMutableSource,1349 useOpaqueIdentifier: mountOpaqueIdentifier,1350 unstable_isNewReconciler: enableNewReconciler,1351};1352const HooksDispatcherOnUpdate: Dispatcher = {1353 readContext,1354 useCallback: updateCallback,1355 useContext: readContext,1356 useEffect: updateEffect,1357 useImperativeHandle: updateImperativeHandle,1358 useLayoutEffect: updateLayoutEffect,1359 useMemo: updateMemo,1360 useReducer: updateReducer,1361 useRef: updateRef,1362 useState: updateState,1363 useDebugValue: updateDebugValue,1364 useDeferredValue: updateDeferredValue,1365 useTransition: updateTransition,1366 useMutableSource: updateMutableSource,1367 useOpaqueIdentifier: updateOpaqueIdentifier,1368 unstable_isNewReconciler: enableNewReconciler,1369};1370const HooksDispatcherOnRerender: Dispatcher = {1371 readContext,1372 useCallback: updateCallback,1373 useContext: readContext,1374 useEffect: updateEffect,1375 useImperativeHandle: updateImperativeHandle,1376 useLayoutEffect: updateLayoutEffect,1377 useMemo: updateMemo,1378 useReducer: rerenderReducer,1379 useRef: updateRef,1380 useState: rerenderState,1381 useDebugValue: updateDebugValue,1382 useDeferredValue: rerenderDeferredValue,1383 useTransition: rerenderTransition,1384 useMutableSource: updateMutableSource,1385 useOpaqueIdentifier: rerenderOpaqueIdentifier,1386 unstable_isNewReconciler: enableNewReconciler,...
ReactFiberHooks.old.js
Source:ReactFiberHooks.old.js
1/**2 * Copyright (c) Facebook, Inc. and its affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */9import type {10 MutableSource,11 MutableSourceGetSnapshotFn,12 MutableSourceSubscribeFn,13} from 'shared/ReactTypes';14import type {Fiber, Dispatcher} from './ReactInternalTypes';15import type {Lanes, Lane} from './ReactFiberLane';16import type {HookFlags} from './ReactHookEffectTags';17import type {ReactPriorityLevel} from './ReactInternalTypes';18import type {FiberRoot} from './ReactInternalTypes';19import type {OpaqueIDType} from './ReactFiberHostConfig';20import ReactSharedInternals from 'shared/ReactSharedInternals';21import {22 enableSchedulingProfiler,23 enableNewReconciler,24 decoupleUpdatePriorityFromScheduler,25} from 'shared/ReactFeatureFlags';26import {NoMode, BlockingMode} from './ReactTypeOfMode';27import {28 NoLane,29 NoLanes,30 InputContinuousLanePriority,31 isSubsetOfLanes,32 mergeLanes,33 removeLanes,34 markRootEntangled,35 markRootMutableRead,36 getCurrentUpdateLanePriority,37 setCurrentUpdateLanePriority,38 higherLanePriority,39 DefaultLanePriority,40} from './ReactFiberLane';41import {readContext} from './ReactFiberNewContext.old';42import {43 Update as UpdateEffect,44 Passive as PassiveEffect,45} from './ReactFiberFlags';46import {47 HasEffect as HookHasEffect,48 Layout as HookLayout,49 Passive as HookPassive,50} from './ReactHookEffectTags';51import {52 getWorkInProgressRoot,53 scheduleUpdateOnFiber,54 requestUpdateLane,55 requestEventTime,56 markSkippedUpdateLanes,57} from './ReactFiberWorkLoop.old';58import invariant from 'shared/invariant';59import is from 'shared/objectIs';60import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.old';61import {62 UserBlockingPriority,63 NormalPriority,64 runWithPriority,65 getCurrentPriorityLevel,66} from './SchedulerWithReactIntegration.old';67import {getIsHydrating} from './ReactFiberHydrationContext.old';68import {69 makeClientId,70 makeOpaqueHydratingObject,71} from './ReactFiberHostConfig';72import {73 getWorkInProgressVersion,74 markSourceAsDirty,75 setWorkInProgressVersion,76} from './ReactMutableSource.old';77import {markStateUpdateScheduled} from './SchedulingProfiler';78const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;79type Update<S, A> = {|80 lane: Lane,81 action: A,82 eagerReducer: ((S, A) => S) | null,83 eagerState: S | null,84 next: Update<S, A>,85 priority?: ReactPriorityLevel,86|};87type UpdateQueue<S, A> = {|88 pending: Update<S, A> | null,89 dispatch: (A => mixed) | null,90 lastRenderedReducer: ((S, A) => S) | null,91 lastRenderedState: S | null,92|};93export type HookType =94 | 'useState'95 | 'useReducer'96 | 'useContext'97 | 'useRef'98 | 'useEffect'99 | 'useLayoutEffect'100 | 'useCallback'101 | 'useMemo'102 | 'useImperativeHandle'103 | 'useDebugValue'104 | 'useDeferredValue'105 | 'useTransition'106 | 'useMutableSource'107 | 'useOpaqueIdentifier';108let didWarnAboutMismatchedHooksForComponent;109let didWarnAboutUseOpaqueIdentifier;110export type Hook = {|111 memoizedState: any,112 baseState: any,113 baseQueue: Update<any, any> | null,114 queue: UpdateQueue<any, any> | null,115 next: Hook | null,116|};117export type Effect = {|118 tag: HookFlags,119 create: () => (() => void) | void,120 destroy: (() => void) | void,121 deps: Array<mixed> | null,122 next: Effect,123|};124export type FunctionComponentUpdateQueue = {|lastEffect: Effect | null|};125type BasicStateAction<S> = (S => S) | S;126type Dispatch<A> = A => void;127// These are set right before calling the component.128let renderLanes: Lanes = NoLanes;129// The work-in-progress fiber. I've named it differently to distinguish it from130// the work-in-progress hook.131let currentlyRenderingFiber: Fiber = (null: any);132// Hooks are stored as a linked list on the fiber's memoizedState field. The133// current hook list is the list that belongs to the current fiber. The134// work-in-progress hook list is a new list that will be added to the135// work-in-progress fiber.136let currentHook: Hook | null = null;137let workInProgressHook: Hook | null = null;138// Whether an update was scheduled at any point during the render phase. This139// does not get reset if we do another render pass; only when we're completely140// finished evaluating this component. This is an optimization so we know141// whether we need to clear render phase updates after a throw.142let didScheduleRenderPhaseUpdate: boolean = false;143// Where an update was scheduled only during the current render pass. This144// gets reset after each attempt.145// TODO: Maybe there's some way to consolidate this with146// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.147let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;148const RE_RENDER_LIMIT = 25;149// In DEV, this is the name of the currently executing primitive hook150let currentHookNameInDev: ?HookType = null;151// In DEV, this list ensures that hooks are called in the same order between renders.152// The list stores the order of hooks used during the initial render (mount).153// Subsequent renders (updates) reference this list.154let hookTypesDev: Array<HookType> | null = null;155let hookTypesUpdateIndexDev: number = -1;156// In DEV, this tracks whether currently rendering component needs to ignore157// the dependencies for Hooks that need them (e.g. useEffect or useMemo).158// When true, such Hooks will always be "remounted". Only used during hot reload.159let ignorePreviousDependencies: boolean = false;160function throwInvalidHookError() {161 invariant(162 false,163 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +164 ' one of the following reasons:\n' +165 '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +166 '2. You might be breaking the Rules of Hooks\n' +167 '3. You might have more than one copy of React in the same app\n' +168 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',169 );170}171function areHookInputsEqual(172 nextDeps: Array<mixed>,173 prevDeps: Array<mixed> | null,174) {175 if (prevDeps === null) {176 return false;177 }178 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {179 if (is(nextDeps[i], prevDeps[i])) {180 continue;181 }182 return false;183 }184 return true;185}186export function renderWithHooks<Props, SecondArg>(187 current: Fiber | null,188 workInProgress: Fiber,189 Component: (p: Props, arg: SecondArg) => any,190 props: Props,191 secondArg: SecondArg,192 nextRenderLanes: Lanes,193): any {194 renderLanes = nextRenderLanes;195 currentlyRenderingFiber = workInProgress;196 workInProgress.memoizedState = null;197 workInProgress.updateQueue = null;198 workInProgress.lanes = NoLanes;199 // The following should have already been reset200 // currentHook = null;201 // workInProgressHook = null;202 // didScheduleRenderPhaseUpdate = false;203 // TODO Warn if no hooks are used at all during mount, then some are used during update.204 // Currently we will identify the update render as a mount because memoizedState === null.205 // This is tricky because it's valid for certain types of components (e.g. React.lazy)206 // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.207 // Non-stateful hooks (e.g. context) don't get added to memoizedState,208 // so memoizedState would be null during updates and mounts.209 ReactCurrentDispatcher.current =210 current === null || current.memoizedState === null211 ? HooksDispatcherOnMount212 : HooksDispatcherOnUpdate;213 let children = Component(props, secondArg);214 // Check if there was a render phase update215 if (didScheduleRenderPhaseUpdateDuringThisPass) {216 // Keep rendering in a loop for as long as render phase updates continue to217 // be scheduled. Use a counter to prevent infinite loops.218 let numberOfReRenders: number = 0;219 do {220 didScheduleRenderPhaseUpdateDuringThisPass = false;221 invariant(222 numberOfReRenders < RE_RENDER_LIMIT,223 'Too many re-renders. React limits the number of renders to prevent ' +224 'an infinite loop.',225 );226 numberOfReRenders += 1;227 // Start over from the beginning of the list228 currentHook = null;229 workInProgressHook = null;230 workInProgress.updateQueue = null;231 ReactCurrentDispatcher.current = HooksDispatcherOnRerender;232 children = Component(props, secondArg);233 } while (didScheduleRenderPhaseUpdateDuringThisPass);234 }235 // We can assume the previous dispatcher is always this one, since we set it236 // at the beginning of the render phase and there's no re-entrancy.237 ReactCurrentDispatcher.current = ContextOnlyDispatcher;238 // This check uses currentHook so that it works the same in DEV and prod bundles.239 // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.240 const didRenderTooFewHooks =241 currentHook !== null && currentHook.next !== null;242 renderLanes = NoLanes;243 currentlyRenderingFiber = (null: any);244 currentHook = null;245 workInProgressHook = null;246 didScheduleRenderPhaseUpdate = false;247 invariant(248 !didRenderTooFewHooks,249 'Rendered fewer hooks than expected. This may be caused by an accidental ' +250 'early return statement.',251 );252 return children;253}254export function bailoutHooks(255 current: Fiber,256 workInProgress: Fiber,257 lanes: Lanes,258) {259 workInProgress.updateQueue = current.updateQueue;260 workInProgress.flags &= ~(PassiveEffect | UpdateEffect);261 current.lanes = removeLanes(current.lanes, lanes);262}263export function resetHooksAfterThrow(): void {264 // We can assume the previous dispatcher is always this one, since we set it265 // at the beginning of the render phase and there's no re-entrancy.266 ReactCurrentDispatcher.current = ContextOnlyDispatcher;267 if (didScheduleRenderPhaseUpdate) {268 // There were render phase updates. These are only valid for this render269 // phase, which we are now aborting. Remove the updates from the queues so270 // they do not persist to the next render. Do not remove updates from hooks271 // that weren't processed.272 //273 // Only reset the updates from the queue if it has a clone. If it does274 // not have a clone, that means it wasn't processed, and the updates were275 // scheduled before we entered the render phase.276 let hook: Hook | null = currentlyRenderingFiber.memoizedState;277 while (hook !== null) {278 const queue = hook.queue;279 if (queue !== null) {280 queue.pending = null;281 }282 hook = hook.next;283 }284 didScheduleRenderPhaseUpdate = false;285 }286 renderLanes = NoLanes;287 currentlyRenderingFiber = (null: any);288 currentHook = null;289 workInProgressHook = null;290 didScheduleRenderPhaseUpdateDuringThisPass = false;291}292function mountWorkInProgressHook(): Hook {293 const hook: Hook = {294 memoizedState: null,295 baseState: null,296 baseQueue: null,297 queue: null,298 next: null,299 };300 if (workInProgressHook === null) {301 // This is the first hook in the list302 currentlyRenderingFiber.memoizedState = workInProgressHook = hook;303 } else {304 // Append to the end of the list305 workInProgressHook = workInProgressHook.next = hook;306 }307 return workInProgressHook;308}309function updateWorkInProgressHook(): Hook {310 // This function is used both for updates and for re-renders triggered by a311 // render phase update. It assumes there is either a current hook we can312 // clone, or a work-in-progress hook from a previous render pass that we can313 // use as a base. When we reach the end of the base list, we must switch to314 // the dispatcher used for mounts.315 let nextCurrentHook: null | Hook;316 if (currentHook === null) {317 const current = currentlyRenderingFiber.alternate;318 if (current !== null) {319 nextCurrentHook = current.memoizedState;320 } else {321 nextCurrentHook = null;322 }323 } else {324 nextCurrentHook = currentHook.next;325 }326 let nextWorkInProgressHook: null | Hook;327 if (workInProgressHook === null) {328 nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;329 } else {330 nextWorkInProgressHook = workInProgressHook.next;331 }332 if (nextWorkInProgressHook !== null) {333 // There's already a work-in-progress. Reuse it.334 workInProgressHook = nextWorkInProgressHook;335 nextWorkInProgressHook = workInProgressHook.next;336 currentHook = nextCurrentHook;337 } else {338 // Clone from the current hook.339 invariant(340 nextCurrentHook !== null,341 'Rendered more hooks than during the previous render.',342 );343 currentHook = nextCurrentHook;344 const newHook: Hook = {345 memoizedState: currentHook.memoizedState,346 baseState: currentHook.baseState,347 baseQueue: currentHook.baseQueue,348 queue: currentHook.queue,349 next: null,350 };351 if (workInProgressHook === null) {352 // This is the first hook in the list.353 currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;354 } else {355 // Append to the end of the list.356 workInProgressHook = workInProgressHook.next = newHook;357 }358 }359 return workInProgressHook;360}361function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {362 return {363 lastEffect: null,364 };365}366function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {367 // $FlowFixMe: Flow doesn't like mixed types368 return typeof action === 'function' ? action(state) : action;369}370function mountReducer<S, I, A>(371 reducer: (S, A) => S,372 initialArg: I,373 init?: I => S,374): [S, Dispatch<A>] {375 const hook = mountWorkInProgressHook();376 let initialState;377 if (init !== undefined) {378 initialState = init(initialArg);379 } else {380 initialState = ((initialArg: any): S);381 }382 hook.memoizedState = hook.baseState = initialState;383 const queue = (hook.queue = {384 pending: null,385 dispatch: null,386 lastRenderedReducer: reducer,387 lastRenderedState: (initialState: any),388 });389 const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(390 null,391 currentlyRenderingFiber,392 queue,393 ): any));394 return [hook.memoizedState, dispatch];395}396function updateReducer<S, I, A>(397 reducer: (S, A) => S,398 initialArg: I,399 init?: I => S,400): [S, Dispatch<A>] {401 const hook = updateWorkInProgressHook();402 const queue = hook.queue;403 invariant(404 queue !== null,405 'Should have a queue. This is likely a bug in React. Please file an issue.',406 );407 queue.lastRenderedReducer = reducer;408 const current: Hook = (currentHook: any);409 // The last rebase update that is NOT part of the base state.410 let baseQueue = current.baseQueue;411 // The last pending update that hasn't been processed yet.412 const pendingQueue = queue.pending;413 if (pendingQueue !== null) {414 // We have new updates that haven't been processed yet.415 // We'll add them to the base queue.416 if (baseQueue !== null) {417 // Merge the pending queue and the base queue.418 const baseFirst = baseQueue.next;419 const pendingFirst = pendingQueue.next;420 baseQueue.next = pendingFirst;421 pendingQueue.next = baseFirst;422 }423 current.baseQueue = baseQueue = pendingQueue;424 queue.pending = null;425 }426 if (baseQueue !== null) {427 // We have a queue to process.428 const first = baseQueue.next;429 let newState = current.baseState;430 let newBaseState = null;431 let newBaseQueueFirst = null;432 let newBaseQueueLast = null;433 let update = first;434 do {435 const updateLane = update.lane;436 if (!isSubsetOfLanes(renderLanes, updateLane)) {437 // Priority is insufficient. Skip this update. If this is the first438 // skipped update, the previous update/state is the new base439 // update/state.440 const clone: Update<S, A> = {441 lane: updateLane,442 action: update.action,443 eagerReducer: update.eagerReducer,444 eagerState: update.eagerState,445 next: (null: any),446 };447 if (newBaseQueueLast === null) {448 newBaseQueueFirst = newBaseQueueLast = clone;449 newBaseState = newState;450 } else {451 newBaseQueueLast = newBaseQueueLast.next = clone;452 }453 // Update the remaining priority in the queue.454 // TODO: Don't need to accumulate this. Instead, we can remove455 // renderLanes from the original lanes.456 currentlyRenderingFiber.lanes = mergeLanes(457 currentlyRenderingFiber.lanes,458 updateLane,459 );460 markSkippedUpdateLanes(updateLane);461 } else {462 // This update does have sufficient priority.463 if (newBaseQueueLast !== null) {464 const clone: Update<S, A> = {465 // This update is going to be committed so we never want uncommit466 // it. Using NoLane works because 0 is a subset of all bitmasks, so467 // this will never be skipped by the check above.468 lane: NoLane,469 action: update.action,470 eagerReducer: update.eagerReducer,471 eagerState: update.eagerState,472 next: (null: any),473 };474 newBaseQueueLast = newBaseQueueLast.next = clone;475 }476 // Process this update.477 if (update.eagerReducer === reducer) {478 // If this update was processed eagerly, and its reducer matches the479 // current reducer, we can use the eagerly computed state.480 newState = ((update.eagerState: any): S);481 } else {482 const action = update.action;483 newState = reducer(newState, action);484 }485 }486 update = update.next;487 } while (update !== null && update !== first);488 if (newBaseQueueLast === null) {489 newBaseState = newState;490 } else {491 newBaseQueueLast.next = (newBaseQueueFirst: any);492 }493 // Mark that the fiber performed work, but only if the new state is494 // different from the current state.495 if (!is(newState, hook.memoizedState)) {496 markWorkInProgressReceivedUpdate();497 }498 hook.memoizedState = newState;499 hook.baseState = newBaseState;500 hook.baseQueue = newBaseQueueLast;501 queue.lastRenderedState = newState;502 }503 const dispatch: Dispatch<A> = (queue.dispatch: any);504 return [hook.memoizedState, dispatch];505}506function rerenderReducer<S, I, A>(507 reducer: (S, A) => S,508 initialArg: I,509 init?: I => S,510): [S, Dispatch<A>] {511 const hook = updateWorkInProgressHook();512 const queue = hook.queue;513 invariant(514 queue !== null,515 'Should have a queue. This is likely a bug in React. Please file an issue.',516 );517 queue.lastRenderedReducer = reducer;518 // This is a re-render. Apply the new render phase updates to the previous519 // work-in-progress hook.520 const dispatch: Dispatch<A> = (queue.dispatch: any);521 const lastRenderPhaseUpdate = queue.pending;522 let newState = hook.memoizedState;523 if (lastRenderPhaseUpdate !== null) {524 // The queue doesn't persist past this render pass.525 queue.pending = null;526 const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;527 let update = firstRenderPhaseUpdate;528 do {529 // Process this render phase update. We don't have to check the530 // priority because it will always be the same as the current531 // render's.532 const action = update.action;533 newState = reducer(newState, action);534 update = update.next;535 } while (update !== firstRenderPhaseUpdate);536 // Mark that the fiber performed work, but only if the new state is537 // different from the current state.538 if (!is(newState, hook.memoizedState)) {539 markWorkInProgressReceivedUpdate();540 }541 hook.memoizedState = newState;542 // Don't persist the state accumulated from the render phase updates to543 // the base state unless the queue is empty.544 // TODO: Not sure if this is the desired semantics, but it's what we545 // do for gDSFP. I can't remember why.546 if (hook.baseQueue === null) {547 hook.baseState = newState;548 }549 queue.lastRenderedState = newState;550 }551 return [newState, dispatch];552}553type MutableSourceMemoizedState<Source, Snapshot> = {|554 refs: {555 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,556 setSnapshot: Snapshot => void,557 },558 source: MutableSource<any>,559 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,560|};561function readFromUnsubcribedMutableSource<Source, Snapshot>(562 root: FiberRoot,563 source: MutableSource<Source>,564 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,565): Snapshot {566 const getVersion = source._getVersion;567 const version = getVersion(source._source);568 // Is it safe for this component to read from this source during the current render?569 let isSafeToReadFromSource = false;570 // Check the version first.571 // If this render has already been started with a specific version,572 // we can use it alone to determine if we can safely read from the source.573 const currentRenderVersion = getWorkInProgressVersion(source);574 if (currentRenderVersion !== null) {575 // It's safe to read if the store hasn't been mutated since the last time576 // we read something.577 isSafeToReadFromSource = currentRenderVersion === version;578 } else {579 // If there's no version, then this is the first time we've read from the580 // source during the current render pass, so we need to do a bit more work.581 // What we need to determine is if there are any hooks that already582 // subscribed to the source, and if so, whether there are any pending583 // mutations that haven't been synchronized yet.584 //585 // If there are no pending mutations, then `root.mutableReadLanes` will be586 // empty, and we know we can safely read.587 //588 // If there *are* pending mutations, we may still be able to safely read589 // if the currently rendering lanes are inclusive of the pending mutation590 // lanes, since that guarantees that the value we're about to read from591 // the source is consistent with the values that we read during the most592 // recent mutation.593 isSafeToReadFromSource = isSubsetOfLanes(594 renderLanes,595 root.mutableReadLanes,596 );597 if (isSafeToReadFromSource) {598 // If it's safe to read from this source during the current render,599 // store the version in case other components read from it.600 // A changed version number will let those components know to throw and restart the render.601 setWorkInProgressVersion(source, version);602 }603 }604 if (isSafeToReadFromSource) {605 const snapshot = getSnapshot(source._source);606 return snapshot;607 } else {608 // This handles the special case of a mutable source being shared between renderers.609 // In that case, if the source is mutated between the first and second renderer,610 // The second renderer don't know that it needs to reset the WIP version during unwind,611 // (because the hook only marks sources as dirty if it's written to their WIP version).612 // That would cause this tear check to throw again and eventually be visible to the user.613 // We can avoid this infinite loop by explicitly marking the source as dirty.614 //615 // This can lead to tearing in the first renderer when it resumes,616 // but there's nothing we can do about that (short of throwing here and refusing to continue the render).617 markSourceAsDirty(source);618 invariant(619 false,620 'Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.',621 );622 }623}624function useMutableSource<Source, Snapshot>(625 hook: Hook,626 source: MutableSource<Source>,627 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,628 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,629): Snapshot {630 const root = ((getWorkInProgressRoot(): any): FiberRoot);631 invariant(632 root !== null,633 'Expected a work-in-progress root. This is a bug in React. Please file an issue.',634 );635 const getVersion = source._getVersion;636 const version = getVersion(source._source);637 const dispatcher = ReactCurrentDispatcher.current;638 // eslint-disable-next-line prefer-const639 let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>640 readFromUnsubcribedMutableSource(root, source, getSnapshot),641 );642 let snapshot = currentSnapshot;643 // Grab a handle to the state hook as well.644 // We use it to clear the pending update queue if we have a new source.645 const stateHook = ((workInProgressHook: any): Hook);646 const memoizedState = ((hook.memoizedState: any): MutableSourceMemoizedState<647 Source,648 Snapshot,649 >);650 const refs = memoizedState.refs;651 const prevGetSnapshot = refs.getSnapshot;652 const prevSource = memoizedState.source;653 const prevSubscribe = memoizedState.subscribe;654 const fiber = currentlyRenderingFiber;655 hook.memoizedState = ({656 refs,657 source,658 subscribe,659 }: MutableSourceMemoizedState<Source, Snapshot>);660 // Sync the values needed by our subscription handler after each commit.661 dispatcher.useEffect(() => {662 refs.getSnapshot = getSnapshot;663 // Normally the dispatch function for a state hook never changes,664 // but this hook recreates the queue in certain cases to avoid updates from stale sources.665 // handleChange() below needs to reference the dispatch function without re-subscribing,666 // so we use a ref to ensure that it always has the latest version.667 refs.setSnapshot = setSnapshot;668 // Check for a possible change between when we last rendered now.669 const maybeNewVersion = getVersion(source._source);670 if (!is(version, maybeNewVersion)) {671 const maybeNewSnapshot = getSnapshot(source._source);672 if (!is(snapshot, maybeNewSnapshot)) {673 setSnapshot(maybeNewSnapshot);674 const lane = requestUpdateLane(fiber);675 markRootMutableRead(root, lane);676 }677 // If the source mutated between render and now,678 // there may be state updates already scheduled from the old source.679 // Entangle the updates so that they render in the same batch.680 markRootEntangled(root, root.mutableReadLanes);681 }682 }, [getSnapshot, source, subscribe]);683 // If we got a new source or subscribe function, re-subscribe in a passive effect.684 dispatcher.useEffect(() => {685 const handleChange = () => {686 const latestGetSnapshot = refs.getSnapshot;687 const latestSetSnapshot = refs.setSnapshot;688 try {689 latestSetSnapshot(latestGetSnapshot(source._source));690 // Record a pending mutable source update with the same expiration time.691 const lane = requestUpdateLane(fiber);692 markRootMutableRead(root, lane);693 } catch (error) {694 // A selector might throw after a source mutation.695 // e.g. it might try to read from a part of the store that no longer exists.696 // In this case we should still schedule an update with React.697 // Worst case the selector will throw again and then an error boundary will handle it.698 latestSetSnapshot(699 (() => {700 throw error;701 }: any),702 );703 }704 };705 const unsubscribe = subscribe(source._source, handleChange);706 return unsubscribe;707 }, [source, subscribe]);708 // If any of the inputs to useMutableSource change, reading is potentially unsafe.709 //710 // If either the source or the subscription have changed we can't can't trust the update queue.711 // Maybe the source changed in a way that the old subscription ignored but the new one depends on.712 //713 // If the getSnapshot function changed, we also shouldn't rely on the update queue.714 // It's possible that the underlying source was mutated between the when the last "change" event fired,715 // and when the current render (with the new getSnapshot function) is processed.716 //717 // In both cases, we need to throw away pending updates (since they are no longer relevant)718 // and treat reading from the source as we do in the mount case.719 if (720 !is(prevGetSnapshot, getSnapshot) ||721 !is(prevSource, source) ||722 !is(prevSubscribe, subscribe)723 ) {724 // Create a new queue and setState method,725 // So if there are interleaved updates, they get pushed to the older queue.726 // When this becomes current, the previous queue and dispatch method will be discarded,727 // including any interleaving updates that occur.728 const newQueue = {729 pending: null,730 dispatch: null,731 lastRenderedReducer: basicStateReducer,732 lastRenderedState: snapshot,733 };734 newQueue.dispatch = setSnapshot = (dispatchAction.bind(735 null,736 currentlyRenderingFiber,737 newQueue,738 ): any);739 stateHook.queue = newQueue;740 stateHook.baseQueue = null;741 snapshot = readFromUnsubcribedMutableSource(root, source, getSnapshot);742 stateHook.memoizedState = stateHook.baseState = snapshot;743 }744 return snapshot;745}746function mountMutableSource<Source, Snapshot>(747 source: MutableSource<Source>,748 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,749 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,750): Snapshot {751 const hook = mountWorkInProgressHook();752 hook.memoizedState = ({753 refs: {754 getSnapshot,755 setSnapshot: (null: any),756 },757 source,758 subscribe,759 }: MutableSourceMemoizedState<Source, Snapshot>);760 return useMutableSource(hook, source, getSnapshot, subscribe);761}762function updateMutableSource<Source, Snapshot>(763 source: MutableSource<Source>,764 getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,765 subscribe: MutableSourceSubscribeFn<Source, Snapshot>,766): Snapshot {767 const hook = updateWorkInProgressHook();768 return useMutableSource(hook, source, getSnapshot, subscribe);769}770function mountState<S>(771 initialState: (() => S) | S,772): [S, Dispatch<BasicStateAction<S>>] {773 const hook = mountWorkInProgressHook();774 if (typeof initialState === 'function') {775 // $FlowFixMe: Flow doesn't like mixed types776 initialState = initialState();777 }778 hook.memoizedState = hook.baseState = initialState;779 const queue = (hook.queue = {780 pending: null,781 dispatch: null,782 lastRenderedReducer: basicStateReducer,783 lastRenderedState: (initialState: any),784 });785 const dispatch: Dispatch<786 BasicStateAction<S>,787 > = (queue.dispatch = (dispatchAction.bind(788 null,789 currentlyRenderingFiber,790 queue,791 ): any));792 return [hook.memoizedState, dispatch];793}794function updateState<S>(795 initialState: (() => S) | S,796): [S, Dispatch<BasicStateAction<S>>] {797 return updateReducer(basicStateReducer, (initialState: any));798}799function rerenderState<S>(800 initialState: (() => S) | S,801): [S, Dispatch<BasicStateAction<S>>] {802 return rerenderReducer(basicStateReducer, (initialState: any));803}804function pushEffect(tag, create, destroy, deps) {805 const effect: Effect = {806 tag,807 create,808 destroy,809 deps,810 // Circular811 next: (null: any),812 };813 let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);814 if (componentUpdateQueue === null) {815 componentUpdateQueue = createFunctionComponentUpdateQueue();816 currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);817 componentUpdateQueue.lastEffect = effect.next = effect;818 } else {819 const lastEffect = componentUpdateQueue.lastEffect;820 if (lastEffect === null) {821 componentUpdateQueue.lastEffect = effect.next = effect;822 } else {823 const firstEffect = lastEffect.next;824 lastEffect.next = effect;825 effect.next = firstEffect;826 componentUpdateQueue.lastEffect = effect;827 }828 }829 return effect;830}831function mountRef<T>(initialValue: T): {|current: T|} {832 const hook = mountWorkInProgressHook();833 const ref = {current: initialValue};834 hook.memoizedState = ref;835 return ref;836}837function updateRef<T>(initialValue: T): {|current: T|} {838 const hook = updateWorkInProgressHook();839 return hook.memoizedState;840}841function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {842 const hook = mountWorkInProgressHook();843 const nextDeps = deps === undefined ? null : deps;844 currentlyRenderingFiber.flags |= fiberFlags;845 hook.memoizedState = pushEffect(846 HookHasEffect | hookFlags,847 create,848 undefined,849 nextDeps,850 );851}852function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {853 const hook = updateWorkInProgressHook();854 const nextDeps = deps === undefined ? null : deps;855 let destroy = undefined;856 if (currentHook !== null) {857 const prevEffect = currentHook.memoizedState;858 destroy = prevEffect.destroy;859 if (nextDeps !== null) {860 const prevDeps = prevEffect.deps;861 if (areHookInputsEqual(nextDeps, prevDeps)) {862 pushEffect(hookFlags, create, destroy, nextDeps);863 return;864 }865 }866 }867 currentlyRenderingFiber.flags |= fiberFlags;868 hook.memoizedState = pushEffect(869 HookHasEffect | hookFlags,870 create,871 destroy,872 nextDeps,873 );874}875function mountEffect(876 create: () => (() => void) | void,877 deps: Array<mixed> | void | null,878): void {879 return mountEffectImpl(880 UpdateEffect | PassiveEffect,881 HookPassive,882 create,883 deps,884 );885}886function updateEffect(887 create: () => (() => void) | void,888 deps: Array<mixed> | void | null,889): void {890 return updateEffectImpl(891 UpdateEffect | PassiveEffect,892 HookPassive,893 create,894 deps,895 );896}897function mountLayoutEffect(898 create: () => (() => void) | void,899 deps: Array<mixed> | void | null,900): void {901 return mountEffectImpl(UpdateEffect, HookLayout, create, deps);902}903function updateLayoutEffect(904 create: () => (() => void) | void,905 deps: Array<mixed> | void | null,906): void {907 return updateEffectImpl(UpdateEffect, HookLayout, create, deps);908}909function imperativeHandleEffect<T>(910 create: () => T,911 ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,912) {913 if (typeof ref === 'function') {914 const refCallback = ref;915 const inst = create();916 refCallback(inst);917 return () => {918 refCallback(null);919 };920 } else if (ref !== null && ref !== undefined) {921 const refObject = ref;922 const inst = create();923 refObject.current = inst;924 return () => {925 refObject.current = null;926 };927 }928}929function mountImperativeHandle<T>(930 ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,931 create: () => T,932 deps: Array<mixed> | void | null,933): void {934 // TODO: If deps are provided, should we skip comparing the ref itself?935 const effectDeps =936 deps !== null && deps !== undefined ? deps.concat([ref]) : null;937 return mountEffectImpl(938 UpdateEffect,939 HookLayout,940 imperativeHandleEffect.bind(null, create, ref),941 effectDeps,942 );943}944function updateImperativeHandle<T>(945 ref: {|current: T | null|} | ((inst: T | null) => mixed) | null | void,946 create: () => T,947 deps: Array<mixed> | void | null,948): void {949 // TODO: If deps are provided, should we skip comparing the ref itself?950 const effectDeps =951 deps !== null && deps !== undefined ? deps.concat([ref]) : null;952 return updateEffectImpl(953 UpdateEffect,954 HookLayout,955 imperativeHandleEffect.bind(null, create, ref),956 effectDeps,957 );958}959function mountDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void {960 // This hook is normally a no-op.961 // The react-debug-hooks package injects its own implementation962 // so that e.g. DevTools can display custom hook values.963}964const updateDebugValue = mountDebugValue;965function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {966 const hook = mountWorkInProgressHook();967 const nextDeps = deps === undefined ? null : deps;968 hook.memoizedState = [callback, nextDeps];969 return callback;970}971function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {972 const hook = updateWorkInProgressHook();973 const nextDeps = deps === undefined ? null : deps;974 const prevState = hook.memoizedState;975 if (prevState !== null) {976 if (nextDeps !== null) {977 const prevDeps: Array<mixed> | null = prevState[1];978 if (areHookInputsEqual(nextDeps, prevDeps)) {979 return prevState[0];980 }981 }982 }983 hook.memoizedState = [callback, nextDeps];984 return callback;985}986function mountMemo<T>(987 nextCreate: () => T,988 deps: Array<mixed> | void | null,989): T {990 const hook = mountWorkInProgressHook();991 const nextDeps = deps === undefined ? null : deps;992 const nextValue = nextCreate();993 hook.memoizedState = [nextValue, nextDeps];994 return nextValue;995}996function updateMemo<T>(997 nextCreate: () => T,998 deps: Array<mixed> | void | null,999): T {1000 const hook = updateWorkInProgressHook();1001 const nextDeps = deps === undefined ? null : deps;1002 const prevState = hook.memoizedState;1003 if (prevState !== null) {1004 // Assume these are defined. If they're not, areHookInputsEqual will warn.1005 if (nextDeps !== null) {1006 const prevDeps: Array<mixed> | null = prevState[1];1007 if (areHookInputsEqual(nextDeps, prevDeps)) {1008 return prevState[0];1009 }1010 }1011 }1012 const nextValue = nextCreate();1013 hook.memoizedState = [nextValue, nextDeps];1014 return nextValue;1015}1016function mountDeferredValue<T>(value: T): T {1017 const [prevValue, setValue] = mountState(value);1018 mountEffect(() => {1019 const prevTransition = ReactCurrentBatchConfig.transition;1020 ReactCurrentBatchConfig.transition = 1;1021 try {1022 setValue(value);1023 } finally {1024 ReactCurrentBatchConfig.transition = prevTransition;1025 }1026 }, [value]);1027 return prevValue;1028}1029function updateDeferredValue<T>(value: T): T {1030 const [prevValue, setValue] = updateState(value);1031 updateEffect(() => {1032 const prevTransition = ReactCurrentBatchConfig.transition;1033 ReactCurrentBatchConfig.transition = 1;1034 try {1035 setValue(value);1036 } finally {1037 ReactCurrentBatchConfig.transition = prevTransition;1038 }1039 }, [value]);1040 return prevValue;1041}1042function rerenderDeferredValue<T>(value: T): T {1043 const [prevValue, setValue] = rerenderState(value);1044 updateEffect(() => {1045 const prevTransition = ReactCurrentBatchConfig.transition;1046 ReactCurrentBatchConfig.transition = 1;1047 try {1048 setValue(value);1049 } finally {1050 ReactCurrentBatchConfig.transition = prevTransition;1051 }1052 }, [value]);1053 return prevValue;1054}1055function startTransition(setPending, callback) {1056 const priorityLevel = getCurrentPriorityLevel();1057 if (decoupleUpdatePriorityFromScheduler) {1058 const previousLanePriority = getCurrentUpdateLanePriority();1059 setCurrentUpdateLanePriority(1060 higherLanePriority(previousLanePriority, InputContinuousLanePriority),1061 );1062 runWithPriority(1063 priorityLevel < UserBlockingPriority1064 ? UserBlockingPriority1065 : priorityLevel,1066 () => {1067 setPending(true);1068 },1069 );1070 // TODO: Can remove this. Was only necessary because we used to give1071 // different behavior to transitions without a config object. Now they are1072 // all treated the same.1073 setCurrentUpdateLanePriority(DefaultLanePriority);1074 runWithPriority(1075 priorityLevel > NormalPriority ? NormalPriority : priorityLevel,1076 () => {1077 const prevTransition = ReactCurrentBatchConfig.transition;1078 ReactCurrentBatchConfig.transition = 1;1079 try {1080 setPending(false);1081 callback();1082 } finally {1083 if (decoupleUpdatePriorityFromScheduler) {1084 setCurrentUpdateLanePriority(previousLanePriority);1085 }1086 ReactCurrentBatchConfig.transition = prevTransition;1087 }1088 },1089 );1090 } else {1091 runWithPriority(1092 priorityLevel < UserBlockingPriority1093 ? UserBlockingPriority1094 : priorityLevel,1095 () => {1096 setPending(true);1097 },1098 );1099 runWithPriority(1100 priorityLevel > NormalPriority ? NormalPriority : priorityLevel,1101 () => {1102 const prevTransition = ReactCurrentBatchConfig.transition;1103 ReactCurrentBatchConfig.transition = 1;1104 try {1105 setPending(false);1106 callback();1107 } finally {1108 ReactCurrentBatchConfig.transition = prevTransition;1109 }1110 },1111 );1112 }1113}1114function mountTransition(): [(() => void) => void, boolean] {1115 const [isPending, setPending] = mountState(false);1116 // The `start` method can be stored on a ref, since `setPending`1117 // never changes.1118 const start = startTransition.bind(null, setPending);1119 mountRef(start);1120 return [start, isPending];1121}1122function updateTransition(): [(() => void) => void, boolean] {1123 const [isPending] = updateState(false);1124 const startRef = updateRef();1125 const start: (() => void) => void = (startRef.current: any);1126 return [start, isPending];1127}1128function rerenderTransition(): [(() => void) => void, boolean] {1129 const [isPending] = rerenderState(false);1130 const startRef = updateRef();1131 const start: (() => void) => void = (startRef.current: any);1132 return [start, isPending];1133}1134function mountOpaqueIdentifier(): OpaqueIDType | void {1135 const makeId = makeClientId;1136 if (getIsHydrating()) {1137 let didUpgrade = false;1138 const fiber = currentlyRenderingFiber;1139 const readValue = () => {1140 if (!didUpgrade) {1141 // Only upgrade once. This works even inside the render phase because1142 // the update is added to a shared queue, which outlasts the1143 // in-progress render.1144 didUpgrade = true;1145 setId(makeId());1146 }1147 invariant(1148 false,1149 'The object passed back from useOpaqueIdentifier is meant to be ' +1150 'passed through to attributes only. Do not read the value directly.',1151 );1152 };1153 const id = makeOpaqueHydratingObject(readValue);1154 const setId = mountState(id)[1];1155 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {1156 currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;1157 pushEffect(1158 HookHasEffect | HookPassive,1159 () => {1160 setId(makeId());1161 },1162 undefined,1163 null,1164 );1165 }1166 return id;1167 } else {1168 const id = makeId();1169 mountState(id);1170 return id;1171 }1172}1173function updateOpaqueIdentifier(): OpaqueIDType | void {1174 const id = updateState(undefined)[0];1175 return id;1176}1177function rerenderOpaqueIdentifier(): OpaqueIDType | void {1178 const id = rerenderState(undefined)[0];1179 return id;1180}1181function dispatchAction<S, A>(1182 fiber: Fiber,1183 queue: UpdateQueue<S, A>,1184 action: A,1185) {1186 const eventTime = requestEventTime();1187 const lane = requestUpdateLane(fiber);1188 const update: Update<S, A> = {1189 lane,1190 action,1191 eagerReducer: null,1192 eagerState: null,1193 next: (null: any),1194 };1195 // Append the update to the end of the list.1196 const pending = queue.pending;1197 if (pending === null) {1198 // This is the first update. Create a circular list.1199 update.next = update;1200 } else {1201 update.next = pending.next;1202 pending.next = update;1203 }1204 queue.pending = update;1205 const alternate = fiber.alternate;1206 if (1207 fiber === currentlyRenderingFiber ||1208 (alternate !== null && alternate === currentlyRenderingFiber)1209 ) {1210 // This is a render phase update. Stash it in a lazily-created map of1211 // queue -> linked list of updates. After this render pass, we'll restart1212 // and apply the stashed updates on top of the work-in-progress hook.1213 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;1214 } else {1215 if (1216 fiber.lanes === NoLanes &&1217 (alternate === null || alternate.lanes === NoLanes)1218 ) {1219 // The queue is currently empty, which means we can eagerly compute the1220 // next state before entering the render phase. If the new state is the1221 // same as the current state, we may be able to bail out entirely.1222 const lastRenderedReducer = queue.lastRenderedReducer;1223 if (lastRenderedReducer !== null) {1224 let prevDispatcher;1225 try {1226 const currentState: S = (queue.lastRenderedState: any);1227 const eagerState = lastRenderedReducer(currentState, action);1228 // Stash the eagerly computed state, and the reducer used to compute1229 // it, on the update object. If the reducer hasn't changed by the1230 // time we enter the render phase, then the eager state can be used1231 // without calling the reducer again.1232 update.eagerReducer = lastRenderedReducer;1233 update.eagerState = eagerState;1234 if (is(eagerState, currentState)) {1235 // Fast path. We can bail out without scheduling React to re-render.1236 // It's still possible that we'll need to rebase this update later,1237 // if the component re-renders for a different reason and by that1238 // time the reducer has changed.1239 return;1240 }1241 } catch (error) {1242 // Suppress the error. It will throw again in the render phase.1243 } 1244 }1245 }1246 scheduleUpdateOnFiber(fiber, lane, eventTime);1247 }1248 if (enableSchedulingProfiler) {1249 markStateUpdateScheduled(fiber, lane);1250 }1251}1252export const ContextOnlyDispatcher: Dispatcher = {1253 readContext,1254 useCallback: throwInvalidHookError,1255 useContext: throwInvalidHookError,1256 useEffect: throwInvalidHookError,1257 useImperativeHandle: throwInvalidHookError,1258 useLayoutEffect: throwInvalidHookError,1259 useMemo: throwInvalidHookError,1260 useReducer: throwInvalidHookError,1261 useRef: throwInvalidHookError,1262 useState: throwInvalidHookError,1263 useDebugValue: throwInvalidHookError,1264 useDeferredValue: throwInvalidHookError,1265 useTransition: throwInvalidHookError,1266 useMutableSource: throwInvalidHookError,1267 useOpaqueIdentifier: throwInvalidHookError,1268 unstable_isNewReconciler: enableNewReconciler,1269};1270const HooksDispatcherOnMount: Dispatcher = {1271 readContext,1272 useCallback: mountCallback,1273 useContext: readContext,1274 useEffect: mountEffect,1275 useImperativeHandle: mountImperativeHandle,1276 useLayoutEffect: mountLayoutEffect,1277 useMemo: mountMemo,1278 useReducer: mountReducer,1279 useRef: mountRef,1280 useState: mountState,1281 useDebugValue: mountDebugValue,1282 useDeferredValue: mountDeferredValue,1283 useTransition: mountTransition,1284 useMutableSource: mountMutableSource,1285 useOpaqueIdentifier: mountOpaqueIdentifier,1286 unstable_isNewReconciler: enableNewReconciler,1287};1288const HooksDispatcherOnUpdate: Dispatcher = {1289 readContext,1290 useCallback: updateCallback,1291 useContext: readContext,1292 useEffect: updateEffect,1293 useImperativeHandle: updateImperativeHandle,1294 useLayoutEffect: updateLayoutEffect,1295 useMemo: updateMemo,1296 useReducer: updateReducer,1297 useRef: updateRef,1298 useState: updateState,1299 useDebugValue: updateDebugValue,1300 useDeferredValue: updateDeferredValue,1301 useTransition: updateTransition,1302 useMutableSource: updateMutableSource,1303 useOpaqueIdentifier: updateOpaqueIdentifier,1304 unstable_isNewReconciler: enableNewReconciler,1305};1306const HooksDispatcherOnRerender: Dispatcher = {1307 readContext,1308 useCallback: updateCallback,1309 useContext: readContext,1310 useEffect: updateEffect,1311 useImperativeHandle: updateImperativeHandle,1312 useLayoutEffect: updateLayoutEffect,1313 useMemo: updateMemo,1314 useReducer: rerenderReducer,1315 useRef: updateRef,1316 useState: rerenderState,1317 useDebugValue: updateDebugValue,1318 useDeferredValue: rerenderDeferredValue,1319 useTransition: rerenderTransition,1320 useMutableSource: updateMutableSource,1321 useOpaqueIdentifier: rerenderOpaqueIdentifier,1322 unstable_isNewReconciler: enableNewReconciler,...
ReactFiberHooks.js
Source:ReactFiberHooks.js
1/**2 * Copyright (c) Facebook, Inc. and its affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */9import type {10 ReactEventResponder,11 ReactContext,12 ReactEventResponderListener,13} from 'shared/ReactTypes';14import type {SideEffectTag} from 'shared/ReactSideEffectTags';15import type {Fiber} from './ReactFiber';16import type {ExpirationTime} from './ReactFiberExpirationTime';17import type {HookEffectTag} from './ReactHookEffectTags';18import type {SuspenseConfig} from './ReactFiberSuspenseConfig';19import type {ReactPriorityLevel} from './SchedulerWithReactIntegration';20import ReactSharedInternals from 'shared/ReactSharedInternals';21import {NoWork} from './ReactFiberExpirationTime';22import {readContext} from './ReactFiberNewContext';23import {createResponderListener} from './ReactFiberEvents';24import {25 Update as UpdateEffect,26 Passive as PassiveEffect,27} from 'shared/ReactSideEffectTags';28import {29 NoEffect as NoHookEffect,30 UnmountMutation,31 MountLayout,32 UnmountPassive,33 MountPassive,34} from './ReactHookEffectTags';35import {36 scheduleWork,37 computeExpirationForFiber,38 requestCurrentTime,39 warnIfNotCurrentlyActingEffectsInDEV,40 warnIfNotCurrentlyActingUpdatesInDev,41 warnIfNotScopedWithMatchingAct,42 markRenderEventTimeAndConfig,43} from './ReactFiberWorkLoop';44import invariant from 'shared/invariant';45import warning from 'shared/warning';46import getComponentName from 'shared/getComponentName';47import is from '../shared/objectIs';48import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork';49import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';50import {getCurrentPriorityLevel} from './SchedulerWithReactIntegration';51const {ReactCurrentDispatcher} = ReactSharedInternals;52export type Dispatcher = {53 readContext<T>(54 context: ReactContext<T>,55 observedBits: void | number | boolean,56 ): T,57 useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>],58 useReducer<S, I, A>(59 reducer: (S, A) => S,60 initialArg: I,61 init?: (I) => S,62 ): [S, Dispatch<A>],63 useContext<T>(64 context: ReactContext<T>,65 observedBits: void | number | boolean,66 ): T,67 useRef<T>(initialValue: T): {current: T},68 useEffect(69 create: () => (() => void) | void,70 deps: Array<mixed> | void | null,71 ): void,72 useLayoutEffect(73 create: () => (() => void) | void,74 deps: Array<mixed> | void | null,75 ): void,76 useCallback<T>(callback: T, deps: Array<mixed> | void | null): T,77 useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T,78 useImperativeHandle<T>(79 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,80 create: () => T,81 deps: Array<mixed> | void | null,82 ): void,83 useDebugValue<T>(value: T, formatterFn: ?(value: T) => mixed): void,84 useResponder<E, C>(85 responder: ReactEventResponder<E, C>,86 props: Object,87 ): ReactEventResponderListener<E, C>,88};89type Update<S, A> = {90 expirationTime: ExpirationTime,91 suspenseConfig: null | SuspenseConfig,92 action: A,93 eagerReducer: ((S, A) => S) | null,94 eagerState: S | null,95 next: Update<S, A> | null,96 priority?: ReactPriorityLevel,97};98type UpdateQueue<S, A> = {99 last: Update<S, A> | null,100 dispatch: (A => mixed) | null,101 lastRenderedReducer: ((S, A) => S) | null,102 lastRenderedState: S | null,103};104export type HookType =105 | 'useState'106 | 'useReducer'107 | 'useContext'108 | 'useRef'109 | 'useEffect'110 | 'useLayoutEffect'111 | 'useCallback'112 | 'useMemo'113 | 'useImperativeHandle'114 | 'useDebugValue'115 | 'useResponder';116export type Hook = {117 memoizedState: any,118 baseState: any,119 baseUpdate: Update<any, any> | null,120 queue: UpdateQueue<any, any> | null,121 next: Hook | null,122};123type Effect = {124 tag: HookEffectTag,125 create: () => (() => void) | void,126 destroy: (() => void) | void,127 deps: Array<mixed> | null,128 next: Effect,129};130export type FunctionComponentUpdateQueue = {131 lastEffect: Effect | null,132};133type BasicStateAction<S> = (S => S) | S;134type Dispatch<A> = A => void;135let renderExpirationTime: ExpirationTime = NoWork;136let currentlyRenderingFiber: Fiber | null = null;137let currentHook: Hook | null = null;138let nextCurrentHook: Hook | null = null;139let firstWorkInProgressHook: Hook | null = null;140let workInProgressHook: Hook | null = null;141let nextWorkInProgressHook: Hook | null = null;142let remainingExpirationTime: ExpirationTime = NoWork;143let componentUpdateQueue: FunctionComponentUpdateQueue | null = null;144let sideEffectTag: SideEffectTag = 0;145let didScheduleRenderPhaseUpdate: boolean = false;146let renderPhaseUpdates: Map<147 UpdateQueue<any, any>,148 Update<any, any>,149> | null = null;150let numberOfReRenders: number = 0;151const RE_RENDER_LIMIT = 25;152let currentHookNameInDev: ?HookType = null;153let hookTypesDev: Array<HookType> | null = null;154let hookTypesUpdateIndexDev: number = -1;155let ignorePreviousDependencies: boolean = false;156// å¤æHookæ¯å¦å¨å½æ°ç»ä»¶å
é¨è°ç¨157function throwInvalidHookError() {158 invariant(159 false,160 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +161 ' one of the following reasons:\n' +162 '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +163 '2. You might be breaking the Rules of Hooks\n' +164 '3. You might have more than one copy of React in the same app\n' +165 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',166 );167}168function areHookInputsEqual(169 nextDeps: Array<mixed>,170 prevDeps: Array<mixed> | null,171) {172 if (prevDeps === null) {173 return false;174 }175 for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {176 if (is(nextDeps[i], prevDeps[i])) {177 continue;178 }179 return false;180 }181 return true;182}183// æ§è¡å½æ°ï¼è¿åç»æ(children)184export function renderWithHooks(185 current: Fiber | null,186 workInProgress: Fiber,187 Component: any,188 props: any,189 refOrContext: any,190 nextRenderExpirationTime: ExpirationTime,191): any {192 renderExpirationTime = nextRenderExpirationTime;193 currentlyRenderingFiber = workInProgress;194 nextCurrentHook = current !== null ? current.memoizedState : null;195 // The following should have already been reset196 // currentHook = null;197 // workInProgressHook = null;198 // remainingExpirationTime = NoWork;199 // componentUpdateQueue = null;200 // didScheduleRenderPhaseUpdate = false;201 // renderPhaseUpdates = null;202 // numberOfReRenders = 0;203 // sideEffectTag = 0;204 // TODO Warn if no hooks are used at all during mount, then some are used during update.205 // Currently we will identify the update render as a mount because nextCurrentHook === null.206 // This is tricky because it's valid for certain types of components (e.g. React.lazy)207 // Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.208 // Non-stateful hooks (e.g. context) don't get added to memoizedState,209 // so nextCurrentHook would be null during updates and mounts.210 ReactCurrentDispatcher.current =211 nextCurrentHook === null212 ? HooksDispatcherOnMount213 : HooksDispatcherOnUpdate;214 // å½æ°æ§è¡ï¼ä¼ å
¥propsårefæè
contextï¼å¾å°children215 let children = Component(props, refOrContext);216 if (didScheduleRenderPhaseUpdate) {217 do {218 didScheduleRenderPhaseUpdate = false;219 numberOfReRenders += 1;220 // Start over from the beginning of the list221 nextCurrentHook = current !== null ? current.memoizedState : null;222 nextWorkInProgressHook = firstWorkInProgressHook;223 currentHook = null;224 workInProgressHook = null;225 componentUpdateQueue = null;226 ReactCurrentDispatcher.current = HooksDispatcherOnUpdate227 children = Component(props, refOrContext);228 } while (didScheduleRenderPhaseUpdate);229 renderPhaseUpdates = null;230 numberOfReRenders = 0;231 }232 // We can assume the previous dispatcher is always this one, since we set it233 // at the beginning of the render phase and there's no re-entrancy.234 ReactCurrentDispatcher.current = ContextOnlyDispatcher;235 const renderedWork: Fiber = currentlyRenderingFiber;236 renderedWork.memoizedState = firstWorkInProgressHook;237 renderedWork.expirationTime = remainingExpirationTime;238 renderedWork.updateQueue = componentUpdateQueue;239 renderedWork.effectTag = sideEffectTag;240 // This check uses currentHook so that it works the same in DEV and prod bundles.241 // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.242 const didRenderTooFewHooks =243 currentHook !== null && currentHook.next !== null;244 renderExpirationTime = NoWork;245 currentlyRenderingFiber = null;246 currentHook = null;247 nextCurrentHook = null;248 firstWorkInProgressHook = null;249 workInProgressHook = null;250 nextWorkInProgressHook = null;251 remainingExpirationTime = NoWork;252 componentUpdateQueue = null;253 sideEffectTag = 0;254 return children;255}256export function bailoutHooks(257 current: Fiber,258 workInProgress: Fiber,259 expirationTime: ExpirationTime,260) {261 workInProgress.updateQueue = current.updateQueue;262 workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);263 if (current.expirationTime <= expirationTime) {264 current.expirationTime = NoWork;265 }266}267export function resetHooks(): void {268 ReactCurrentDispatcher.current = ContextOnlyDispatcher;269 renderExpirationTime = NoWork;270 currentlyRenderingFiber = null;271 currentHook = null;272 nextCurrentHook = null;273 firstWorkInProgressHook = null;274 workInProgressHook = null;275 nextWorkInProgressHook = null;276 remainingExpirationTime = NoWork;277 componentUpdateQueue = null;278 sideEffectTag = 0;279 didScheduleRenderPhaseUpdate = false;280 renderPhaseUpdates = null;281 numberOfReRenders = 0;282}283function mountWorkInProgressHook(): Hook {284 const hook: Hook = {285 memoizedState: null,286 baseState: null,287 queue: null,288 baseUpdate: null,289 next: null,290 };291 if (workInProgressHook === null) {292 // This is the first hook in the list293 firstWorkInProgressHook = workInProgressHook = hook;294 } else {295 // Append to the end of the list296 workInProgressHook = workInProgressHook.next = hook;297 }298 return workInProgressHook;299}300function updateWorkInProgressHook(): Hook {301 if (nextWorkInProgressHook !== null) {302 workInProgressHook = nextWorkInProgressHook;303 nextWorkInProgressHook = workInProgressHook.next;304 currentHook = nextCurrentHook;305 nextCurrentHook = currentHook !== null ? currentHook.next : null;306 } else {307 currentHook = nextCurrentHook;308 const newHook: Hook = {309 memoizedState: currentHook.memoizedState,310 baseState: currentHook.baseState,311 queue: currentHook.queue,312 baseUpdate: currentHook.baseUpdate,313 next: null,314 };315 if (workInProgressHook === null) {316 workInProgressHook = firstWorkInProgressHook = newHook;317 } else {318 workInProgressHook = workInProgressHook.next = newHook;319 }320 nextCurrentHook = currentHook.next;321 }322 return workInProgressHook;323}324function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {325 return {326 lastEffect: null,327 };328}329function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {330 return typeof action === 'function' ? action(state) : action;331}332function mountReducer<S, I, A>(333 reducer: (S, A) => S,334 initialArg: I,335 init?: I => S,336): [S, Dispatch<A>] {337 const hook = mountWorkInProgressHook();338 let initialState;339 340 if (init !== undefined) {341 initialState = init(initialArg);342 } else {343 initialState = initialArg344 }345 hook.memoizedState = hook.baseState = initialState;346 const queue = (hook.queue = {347 last: null,348 dispatch: null,349 lastRenderedReducer: reducer,350 lastRenderedState: initialState,351 });352 const dispatch: Dispatch<A> = queue.dispatch = dispatchAction.bind(353 null,354 currentlyRenderingFiber,355 queue,356 )357 358 return [hook.memoizedState, dispatch];359}360function updateReducer<S, I, A>(361 reducer: (S, A) => S,362 initialArg: I,363 init?: I => S,364): [S, Dispatch<A>] {365 const hook = updateWorkInProgressHook();366 const queue = hook.queue;367 queue.lastRenderedReducer = reducer;368 if (numberOfReRenders > 0) {369 const dispatch: Dispatch<A> = queue.dispatch370 if (renderPhaseUpdates !== null) {371 const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);372 if (firstRenderPhaseUpdate !== undefined) {373 renderPhaseUpdates.delete(queue);374 let newState = hook.memoizedState;375 let update = firstRenderPhaseUpdate;376 377 do {378 const action = update.action;379 newState = reducer(newState, action);380 update = update.next;381 } while (update !== null);382 if (!is(newState, hook.memoizedState)) {383 markWorkInProgressReceivedUpdate();384 }385 hook.memoizedState = newState;386 if (hook.baseUpdate === queue.last) {387 hook.baseState = newState;388 }389 queue.lastRenderedState = newState;390 return [newState, dispatch];391 }392 }393 return [hook.memoizedState, dispatch];394 }395 const last = queue.last;396 const baseUpdate = hook.baseUpdate;397 const baseState = hook.baseState;398 let first;399 if (baseUpdate !== null) {400 if (last !== null) {401 last.next = null;402 }403 first = baseUpdate.next;404 } else {405 first = last !== null ? last.next : null;406 }407 if (first !== null) {408 let newState = baseState;409 let newBaseState = null;410 let newBaseUpdate = null;411 let prevUpdate = baseUpdate;412 let update = first;413 let didSkip = false;414 do {415 const updateExpirationTime = update.expirationTime;416 if (updateExpirationTime < renderExpirationTime) {417 // ä¼å
级ä¸è¶³ãè·³è¿æ¤æ´æ°ã418 if (!didSkip) {419 didSkip = true;420 newBaseUpdate = prevUpdate;421 newBaseState = newState;422 }423 //æ´æ°éåä¸çå©ä½ä¼å
级ã424 if (updateExpirationTime > remainingExpirationTime) {425 remainingExpirationTime = updateExpirationTime;426 }427 } else {428 markRenderEventTimeAndConfig(429 updateExpirationTime,430 update.suspenseConfig,431 );432 if (update.eagerReducer === reducer) {433 newState = update.eagerState434 } else {435 const action = update.action;436 newState = reducer(newState, action);437 }438 }439 prevUpdate = update;440 update = update.next;441 } while (update !== null && update !== first);442 if (!didSkip) {443 newBaseUpdate = prevUpdate;444 newBaseState = newState;445 }446 if (!is(newState, hook.memoizedState)) {447 markWorkInProgressReceivedUpdate();448 }449 hook.memoizedState = newState;450 hook.baseUpdate = newBaseUpdate;451 hook.baseState = newBaseState;452 queue.lastRenderedState = newState;453 }454 const dispatch: Dispatch<A> = (queue.dispatch: any);455 return [hook.memoizedState, dispatch];456}457function mountState<S>(458 initialState: (() => S) | S,459): [S, Dispatch<BasicStateAction<S>>] {460 const hook = mountWorkInProgressHook();461 if (typeof initialState === 'function') {462 initialState = initialState();463 }464 hook.memoizedState = hook.baseState = initialState;465 const queue = hook.queue = {466 last: null,467 dispatch: null,468 lastRenderedReducer: basicStateReducer,469 lastRenderedState: initialState470 }471 const dispatch: Dispatch<472 BasicStateAction<S>,473 > = queue.dispatch = dispatchAction.bind(474 null,475 // Flow doesn't know this is non-null, but we do.476 currentlyRenderingFiber,477 queue,478 )479 return [hook.memoizedState, dispatch];480}481function updateState<S>(482 initialState: (() => S) | S,483): [S, Dispatch<BasicStateAction<S>>] {484 return updateReducer(basicStateReducer, (initialState: any));485}486function pushEffect(tag, create, destroy, deps) {487 const effect: Effect = {488 tag,489 create,490 destroy,491 deps,492 next: null,493 };494 if (componentUpdateQueue === null) {495 componentUpdateQueue = createFunctionComponentUpdateQueue();496 componentUpdateQueue.lastEffect = effect.next = effect;497 } else {498 const lastEffect = componentUpdateQueue.lastEffect;499 if (lastEffect === null) {500 componentUpdateQueue.lastEffect = effect.next = effect;501 } else {502 const firstEffect = lastEffect.next;503 lastEffect.next = effect;504 effect.next = firstEffect;505 componentUpdateQueue.lastEffect = effect;506 }507 }508 return effect;509}510function mountRef<T>(initialValue: T): {current: T} {511 const hook = mountWorkInProgressHook();512 const ref = {current: initialValue};513 hook.memoizedState = ref;514 return ref;515}516function updateRef<T>(initialValue: T): {current: T} {517 const hook = updateWorkInProgressHook();518 return hook.memoizedState;519}520function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {521 const hook = mountWorkInProgressHook();522 const nextDeps = deps === undefined ? null : deps;523 sideEffectTag |= fiberEffectTag;524 hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);525}526function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {527 const hook = updateWorkInProgressHook();528 const nextDeps = deps === undefined ? null : deps;529 let destroy = undefined;530 if (currentHook !== null) {531 const prevEffect = currentHook.memoizedState;532 destroy = prevEffect.destroy;533 if (nextDeps !== null) {534 const prevDeps = prevEffect.deps;535 if (areHookInputsEqual(nextDeps, prevDeps)) {536 pushEffect(NoHookEffect, create, destroy, nextDeps);537 return;538 }539 }540 }541 sideEffectTag |= fiberEffectTag;542 hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);543}544function mountEffect(545 create: () => (() => void) | void,546 deps: Array<mixed> | void | null,547): void {548 return mountEffectImpl(549 UpdateEffect | PassiveEffect,550 UnmountPassive | MountPassive,551 create,552 deps,553 );554}555function updateEffect(556 create: () => (() => void) | void,557 deps: Array<mixed> | void | null,558): void {559 return updateEffectImpl(560 UpdateEffect | PassiveEffect,561 UnmountPassive | MountPassive,562 create,563 deps,564 );565}566function mountLayoutEffect(567 create: () => (() => void) | void,568 deps: Array<mixed> | void | null,569): void {570 return mountEffectImpl(571 UpdateEffect,572 UnmountMutation | MountLayout,573 create,574 deps,575 );576}577function updateLayoutEffect(578 create: () => (() => void) | void,579 deps: Array<mixed> | void | null,580): void {581 return updateEffectImpl(582 UpdateEffect,583 UnmountMutation | MountLayout,584 create,585 deps,586 );587}588function imperativeHandleEffect<T>(589 create: () => T,590 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,591) {592 if (typeof ref === 'function') {593 const refCallback = ref;594 const inst = create();595 refCallback(inst);596 return () => {597 refCallback(null);598 };599 } else if (ref !== null && ref !== undefined) {600 const refObject = ref;601 const inst = create();602 refObject.current = inst;603 return () => {604 refObject.current = null;605 };606 }607}608function mountImperativeHandle<T>(609 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,610 create: () => T,611 deps: Array<mixed> | void | null,612): void {613 //TODOï¼å¦ææä¾äºdepsï¼æ们åºè¯¥è·³è¿æ¯è¾refæ¬èº«åï¼ 614 const effectDeps =615 deps !== null && deps !== undefined ? deps.concat([ref]) : null;616 return mountEffectImpl(617 UpdateEffect,618 UnmountMutation | MountLayout,619 imperativeHandleEffect.bind(null, create, ref),620 effectDeps,621 );622}623function updateImperativeHandle<T>(624 ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,625 create: () => T,626 deps: Array<mixed> | void | null,627): void {628 //TODOï¼å¦ææä¾äºdepsï¼æ们åºè¯¥è·³è¿æ¯è¾refæ¬èº«åï¼629 const effectDeps =630 deps !== null && deps !== undefined ? deps.concat([ref]) : null;631 return updateEffectImpl(632 UpdateEffect,633 UnmountMutation | MountLayout,634 imperativeHandleEffect.bind(null, create, ref),635 effectDeps,636 );637}638function mountDebugValue<T>(value: T, formatterFn: (value: T) => mixed): void {639//è¿ä¸ªé©åé常æ¯æ æä½çã640//react-debug-hookså
注å
¥äºèªå·±çå®ç°641//è¿æ ·å°±å¯ä»¥äºDevToolså¯ä»¥æ¾ç¤ºèªå®ä¹é©åå¼642}643const updateDebugValue = mountDebugValue;644function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {645 const hook = mountWorkInProgressHook();646 const nextDeps = deps === undefined ? null : deps;647 hook.memoizedState = [callback, nextDeps];648 return callback;649}650function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {651 const hook = updateWorkInProgressHook();652 const nextDeps = deps === undefined ? null : deps;653 const prevState = hook.memoizedState;654 if (prevState !== null) {655 if (nextDeps !== null) {656 const prevDeps: Array<mixed> | null = prevState[1];657 if (areHookInputsEqual(nextDeps, prevDeps)) {658 return prevState[0];659 }660 }661 }662 hook.memoizedState = [callback, nextDeps];663 return callback;664}665function mountMemo<T>(666 nextCreate: () => T,667 deps: Array<mixed> | void | null,668): T {669 const hook = mountWorkInProgressHook();670 const nextDeps = deps === undefined ? null : deps;671 const nextValue = nextCreate();672 hook.memoizedState = [nextValue, nextDeps];673 return nextValue;674}675function updateMemo<T>(676 nextCreate: () => T,677 deps: Array<mixed> | void | null,678): T {679 const hook = updateWorkInProgressHook();680 const nextDeps = deps === undefined ? null : deps;681 const prevState = hook.memoizedState;682 if (prevState !== null) {683 if (nextDeps !== null) {684 const prevDeps: Array<mixed> | null = prevState[1];685 if (areHookInputsEqual(nextDeps, prevDeps)) {686 return prevState[0];687 }688 }689 }690 const nextValue = nextCreate();691 hook.memoizedState = [nextValue, nextDeps];692 return nextValue;693}694function dispatchAction<S, A>(695 fiber: Fiber,696 queue: UpdateQueue<S, A>,697 action: A,698) {699 const alternate = fiber.alternate;700 if (701 fiber === currentlyRenderingFiber ||702 (alternate !== null && alternate === currentlyRenderingFiber)703 ) {704 didScheduleRenderPhaseUpdate = true;705 const update: Update<S, A> = {706 expirationTime: renderExpirationTime,707 suspenseConfig: null,708 action,709 eagerReducer: null,710 eagerState: null,711 next: null,712 };713 if (renderPhaseUpdates === null) {714 renderPhaseUpdates = new Map();715 }716 const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);717 if (firstRenderPhaseUpdate === undefined) {718 renderPhaseUpdates.set(queue, update);719 } else {720 let lastRenderPhaseUpdate = firstRenderPhaseUpdate;721 while (lastRenderPhaseUpdate.next !== null) {722 lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;723 }724 lastRenderPhaseUpdate.next = update;725 }726 } else {727 const currentTime = requestCurrentTime();728 const suspenseConfig = requestCurrentSuspenseConfig();729 const expirationTime = computeExpirationForFiber(730 currentTime,731 fiber,732 suspenseConfig,733 );734 const update: Update<S, A> = {735 expirationTime,736 suspenseConfig,737 action,738 eagerReducer: null,739 eagerState: null,740 next: null,741 };742 const last = queue.last;743 if (last === null) {744 //è¿æ¯ç¬¬ä¸æ¬¡æ´æ°ãå建ä¸ä¸ªå¾ªç¯å表745 update.next = update;746 } else {747 const first = last.next;748 if (first !== null) {749 update.next = first;750 }751 last.next = update;752 }753 queue.last = update;754 if (755 fiber.expirationTime === NoWork &&756 (alternate === null || alternate.expirationTime === NoWork)757 ) {758 const lastRenderedReducer = queue.lastRenderedReducer;759 if (lastRenderedReducer !== null) {760 let prevDispatcher;761 try {762 const currentState: S = (queue.lastRenderedState: any);763 const eagerState = lastRenderedReducer(currentState, action);764 update.eagerReducer = lastRenderedReducer;765 update.eagerState = eagerState;766 if (is(eagerState, currentState)) {767 return;768 }769 } catch (error) {770 }771 }772 }773 scheduleWork(fiber, expirationTime);774 }775}776export const ContextOnlyDispatcher: Dispatcher = {777 readContext,778 useCallback: throwInvalidHookError,779 useContext: throwInvalidHookError,780 useEffect: throwInvalidHookError,781 useImperativeHandle: throwInvalidHookError,782 useLayoutEffect: throwInvalidHookError,783 useMemo: throwInvalidHookError,784 useReducer: throwInvalidHookError,785 useRef: throwInvalidHookError,786 useState: throwInvalidHookError,787 useDebugValue: throwInvalidHookError,788 useResponder: throwInvalidHookError,789};790const HooksDispatcherOnMount: Dispatcher = {791 readContext,792 useCallback: mountCallback,793 useContext: readContext,794 useEffect: mountEffect,795 useImperativeHandle: mountImperativeHandle,796 useLayoutEffect: mountLayoutEffect,797 useMemo: mountMemo,798 useReducer: mountReducer,799 useRef: mountRef,800 useState: mountState,801 useDebugValue: mountDebugValue,802 useResponder: createResponderListener,803};804const HooksDispatcherOnUpdate: Dispatcher = {805 readContext,806 useCallback: updateCallback,807 useContext: readContext,808 useEffect: updateEffect,809 useImperativeHandle: updateImperativeHandle,810 useLayoutEffect: updateLayoutEffect,811 useMemo: updateMemo,812 useReducer: updateReducer,813 useRef: updateRef,814 useState: updateState,815 useDebugValue: updateDebugValue,816 useResponder: createResponderListener,...
withHooks.js
Source:withHooks.js
...221}222function updateLayoutEffect(create, deps) {223 return updateEffectImpl(UpdateEffect, UnmountMutation | MountLayout, create, deps);224}225function imperativeHandleEffect(create, ref) {226 if (typeof ref === 'function') {227 const refCallback = ref;228 const inst = create();229 refCallback(inst);230 return () => {231 refCallback(null);232 };233 } else if (ref !== null && ref !== undefined) {234 const refObject = ref;235 const inst = create();236 refObject.current = inst;237 return () => {238 refObject.current = null;239 };...
Using AI Code Generation
1const {chromium} = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 const handle = await page.$('text=Get started');7 const effect = await handle.imperativeHandleEffect();8 console.log(effect);9 await browser.close();10})();11const {chromium} = require('playwright');12(async () => {13 const browser = await chromium.launch();14 const context = await browser.newContext();15 const page = await context.newPage();16 const handle = await page.$('text=Get started');17 const effect = await handle.imperativeHandleEffect();18 console.log(effect);19 await browser.close();20})();21const {chromium} = require('playwright');22(async () => {23 const browser = await chromium.launch();24 const context = await browser.newContext();25 const page = await context.newPage();26 const handle = await page.$('text=Get started');27 const effect = await handle.imperativeHandleEffect();28 console.log(effect);29 await browser.close();30})();31const {chromium} = require('playwright');32(async () => {33 const browser = await chromium.launch();34 const context = await browser.newContext();35 const page = await context.newPage();36 const handle = await page.$('text=Get started');37 const effect = await handle.imperativeHandleEffect();
Using AI Code Generation
1const playwright = require('playwright');2(async () => {3 const browser = await playwright.chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 const elementHandle = await page.$('text=Get started');7 await elementHandle.imperativeHandleEffect('click');8 await browser.close();9})();10ElementHandle.imperativeHandleEffect(action[, options])11const elementHandle = await page.$('text=Get started');12await elementHandle.imperativeHandleEffect('click');13const elementHandle = await page.$('text=Get started');14await elementHandle.imperativeHandleEffect('click', { delay: 1000 });15const elementHandle = await page.$('text=Get started');16await elementHandle.imperativeHandleEffect('click', { modifiers: ['Control', 'Shift'] });17const elementHandle = await page.$('text=Get started');18await elementHandle.imperativeHandleEffect('click', { position: { x: 100, y: 100 } });19const elementHandle = await page.$('text=Get started');20await elementHandle.imperativeHandleEffect('click', { modifiers: ['Control', 'Shift'], delay: 1000 });21ElementHandle.click()
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const page = await browser.newPage();5 await page.screenshot({ path: `example.png` });6 await browser.close();7})();8const { chromium } = require('playwright');9(async () => {10 const browser = await chromium.launch();11 const page = await browser.newPage();12 await page.screenshot({ path: `example.png` });13 await browser.close();14})();15const { chromium } = require('playwright');16(async () => {17 const browser = await chromium.launch();18 const page = await browser.newPage();19 await page.screenshot({ path: `example.png` });20 await browser.close();21})();22const { chromium } = require('playwright');23(async () => {24 const browser = await chromium.launch();25 const page = await browser.newPage();26 await page.screenshot({ path: `example.png` });27 await browser.close();28})();29const { chromium } = require('playwright');30(async () => {31 const browser = await chromium.launch();32 const page = await browser.newPage();33 await page.screenshot({ path: `example.png` });34 await browser.close();35})();36const { chromium } = require('playwright');37(async () => {38 const browser = await chromium.launch();39 const page = await browser.newPage();40 await page.screenshot({ path: `example.png` });41 await browser.close();42})();43const { chromium } = require('playwright');44(async () => {45 const browser = await chromium.launch();46 const page = await browser.newPage();47 await page.screenshot({ path: `example.png`
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const page = await browser.newPage();5 await page.click('text=Get started');6 await page.click('text=Docs');7 await page.click('text=API');8 await page.click('text=class: BrowserContext');9 await page.click('text=method: newPage');10 await page.click('text=See full API reference');11 await browser.close();12})();13import { chromium } from 'playwright';14(async () => {15 const browser = await chromium.launch();16 const page = await browser.newPage();17 await page.click('text=Get started');18 await page.click('text=Docs');19 await page.click('text=API');20 await page.click('text=class: BrowserContext');21 await page.click('text=method: newPage');22 await page.click('text=See full API reference');23 await browser.close();24})();
Using AI Code Generation
1const {chromium} = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const page = await browser.newPage();5 const handle = await page.$('text=Get started');6 const effect = await handle.imperativeHandleEffect();7 console.log(effect);8 await browser.close();9})();10{ type: 'click', modifiers: [], position: { x: 0, y: 0 } }11const {chromium} = require('playwright');12(async () => {13 const browser = await chromium.launch();14 const page = await browser.newPage();15 const handle = await page.$('text=Get started');16 const effect = await handle.imperativeHandleEffect();17 console.log(effect);18 await browser.close();19})();20{ type: 'click', modifiers: [], position: { x: 0, y: 0 } }21const {chromium} = require('playwright');22(async () => {23 const browser = await chromium.launch();24 const page = await browser.newPage();25 const handle = await page.$('text=Get started');26 const effect = await handle.imperativeHandleEffect();27 console.log(effect);28 await browser.close();29})();30{ type: 'click', modifiers: [], position: { x: 0, y: 0 } }31const {chromium} = require('playwright');32(async () => {
Using AI Code Generation
1const { chromium } = require('playwright');2(async () => {3 const browser = await chromium.launch();4 const context = await browser.newContext();5 const page = await context.newPage();6 const element = await page.$('text=Get Started');7 await page.evaluate((element) => {8 element.click();9 }, element);10 await browser.close();11})();
Using AI Code Generation
1import { chromium } from 'playwright';2import { getPlaywrightInternal } from 'playwright/lib/server/playwright';3const browser = await chromium.launch();4const page = await browser.newPage();5const internal = getPlaywrightInternal(browser);6await internal.imperativeHandleEffect(page, 'click', 'button', 'text=Click me');7await internal.imperativeHandleEffect(page, 'check', 'input:checked', 'text=Check me');8await internal.imperativeHandleEffect(page, 'uncheck', 'input:not(:checked)', 'text=Uncheck me');9await internal.imperativeHandleEffect(page, 'fill', 'input', 'text=Fill me', 'some text');10await internal.imperativeHandleEffect(page, 'select', 'select', 'text=Select me', 'second');11await internal.imperativeHandleEffect(page, 'deselect', 'select', 'text=Deselect me', 'second');12await internal.imperativeHandleEffect(page, 'setInputFiles', 'input', 'text=Upload me', '/Users/testuser/Downloads/test.pdf');13await internal.imperativeHandleEffect(page, 'press', 'input', 'text=Press me', 'Enter');14await internal.imperativeHandleEffect(page, 'check', 'input:checked', 'text=Check me');15await internal.imperativeHandleEffect(page, 'uncheck', 'input:not(:checked)', 'text=Uncheck me');16await internal.imperativeHandleEffect(page, 'fill', 'input', 'text=Fill me', 'some text');17await internal.imperativeHandleEffect(page, 'select', 'select', 'text=Select me', 'second');18await internal.imperativeHandleEffect(page, 'deselect', 'select', 'text=Deselect me', 'second');19await internal.imperativeHandleEffect(page, 'setInputFiles', 'input', 'text=Upload me', '/Users/testuser/Downloads/test.pdf');20await internal.imperativeHandleEffect(page, 'press', 'input', 'text=Press me', 'Enter');21await internal.imperativeHandleEffect(page, 'check', 'input:checked', '
Using AI Code Generation
1const { playwright } = require('playwright');2const { Page } = require('playwright/lib/client/page');3const { ElementHandle } = require('playwright/lib/client/elementHandle');4const { JSHandle } = require('playwright/lib/client/jsHandle');5const { ElementHandleChannel } = require('playwright/lib/client/channels');6const { Frame } = require('playwright/lib/client/frame');7const { FrameChannel } = require('playwright/lib/client/channels');8const { Worker } = require('playwright/lib/client/worker');9const { WorkerChannel } = require('playwright/lib/client/channels');10async function main() {11 const browser = await playwright['chromium'].launch();12 const context = await browser.newContext();13 const page = await context.newPage();14 const frame = page.mainFrame();15 const divHandle = await frame.evaluateHandle(() => {16 const div = document.createElement('div');17 div.setAttribute('id', 'test');18 document.body.appendChild(div);19 return div;20 });21 const newFrame = await frame.childFrameElement(divHandle);22 const newElementHandle = await newFrame.evaluateHandle(() => {23 const div = document.createElement('div');24 div.setAttribute('id', 'test2');25 document.body.appendChild(div);26 return div;27 });28 const elementHandle = await frame.evaluateHandle((element) => {29 return element;30 }, newElementHandle);31 const internalElementHandle = new ElementHandle(elementHandle['_channel'], elementHandle['_initializer']);32 const internalFrame = new Frame(internalElementHandle['_context'], internalElementHandle['_initializer'].frame);33 const internalElementHandleChannel = new ElementHandleChannel(internalFrame._connection, internalElementHandle['_guid']);34 const internalFrameChannel = new FrameChannel(internalFrame._connection, internalFrame._guid);35 const internalWorker = new Worker(internalFrameChannel._connection, internalFrameChannel._guid);36 const internalWorkerChannel = new WorkerChannel(internalWorker._connection, internalWorker._guid);37 const internalJSHandle = new JSHandle(internalWorkerChannel._connection, internalWorkerChannel._guid);38 const internalJSHandleChannel = new JSHandleChannel(internal
Using AI Code Generation
1const elementHandle = await page.$('input');2const jsHandle = await elementHandle.evaluateHandle((element) => {3 return element;4});5const textContent = await jsHandle.evaluate((element) => {6 return element.textContent;7});8console.log(textContent);9await browser.close();10const { chromium } = require('playwright');11const browser = await chromium.launch();12const page = await browser.newPage();13const textContent = await page.textContent('input');14console.log(textContent);15await browser.close();16goto() – This method is
Using AI Code Generation
1const { chromium } = require('playwright');2const { getHandle } = require('./playwright-internal-api');3(async () => {4 const browser = await chromium.launch();5 const context = await browser.newContext();6 const page = await context.newPage();7 const handle = await getHandle(page, 'text="Get started"');8 await handle.click();9 await browser.close();10})();11const { internalCallMetadata } = require('./playwright-core/lib/utils/utils');12const { selectors } = require('./playwright-core/lib/server/common/selectors');13const getHandle = async (page, selector) => {14 const metadata = internalCallMetadata();15 const { context, frame } = page;16 const pageImpl = await page._initializedPromise;17 const result = await selectors._querySelectorAll(18 );19 if (result.length === 0) {20 throw new Error(`No element matching selector: ${selector}`);21 }22 const handle = await frame._page._delegate.imperativeHandleEffect(23 );24 return handle;25};26module.exports = { getHandle };27const { internalCallMetadata } = require('../../utils/utils');28const { selectors } = require('./selectors');29const getHandle = async (page, selector) => {30 const metadata = internalCallMetadata();31 const { context, frame } = page;32 const pageImpl = await page._initializedPromise;33 const result = await selectors._querySelectorAll(34 );35 if (result.length === 0) {36 throw new Error(`No element matching selector: ${selector}`);37 }38 const handle = await frame._page._delegate.imperativeHandleEffect(
LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.
Get 100 minutes of automation test minutes FREE!!