How to use imperativeHandleEffect method in Playwright Internal

Best JavaScript code snippet using playwright-internal

ReactFiberHooks.new.js

Source:ReactFiberHooks.new.js Github

copy

Full Screen

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,...

Full Screen

Full Screen

ReactFiberHooks.old.js

Source:ReactFiberHooks.old.js Github

copy

Full Screen

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,...

Full Screen

Full Screen

ReactFiberHooks.js

Source:ReactFiberHooks.js Github

copy

Full Screen

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,...

Full Screen

Full Screen

withHooks.js

Source:withHooks.js Github

copy

Full Screen

...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 };...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

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();

Full Screen

Using AI Code Generation

copy

Full Screen

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()

Full Screen

Using AI Code Generation

copy

Full Screen

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`

Full Screen

Using AI Code Generation

copy

Full Screen

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})();

Full Screen

Using AI Code Generation

copy

Full Screen

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 () => {

Full Screen

Using AI Code Generation

copy

Full Screen

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})();

Full Screen

Using AI Code Generation

copy

Full Screen

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', '

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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(

Full Screen

Playwright tutorial

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.

Chapters:

  1. What is Playwright : Playwright is comparatively new but has gained good popularity. Get to know some history of the Playwright with some interesting facts connected with it.
  2. How To Install Playwright : Learn in detail about what basic configuration and dependencies are required for installing Playwright and run a test. Get a step-by-step direction for installing the Playwright automation framework.
  3. Playwright Futuristic Features: Launched in 2020, Playwright gained huge popularity quickly because of some obliging features such as Playwright Test Generator and Inspector, Playwright Reporter, Playwright auto-waiting mechanism and etc. Read up on those features to master Playwright testing.
  4. What is Component Testing: Component testing in Playwright is a unique feature that allows a tester to test a single component of a web application without integrating them with other elements. Learn how to perform Component testing on the Playwright automation framework.
  5. Inputs And Buttons In Playwright: Every website has Input boxes and buttons; learn about testing inputs and buttons with different scenarios and examples.
  6. Functions and Selectors in Playwright: Learn how to launch the Chromium browser with Playwright. Also, gain a better understanding of some important functions like “BrowserContext,” which allows you to run multiple browser sessions, and “newPage” which interacts with a page.
  7. Handling Alerts and Dropdowns in Playwright : Playwright interact with different types of alerts and pop-ups, such as simple, confirmation, and prompt, and different types of dropdowns, such as single selector and multi-selector get your hands-on with handling alerts and dropdown in Playright testing.
  8. Playwright vs Puppeteer: Get to know about the difference between two testing frameworks and how they are different than one another, which browsers they support, and what features they provide.
  9. Run Playwright Tests on LambdaTest: Playwright testing with LambdaTest leverages test performance to the utmost. You can run multiple Playwright tests in Parallel with the LammbdaTest test cloud. Get a step-by-step guide to run your Playwright test on the LambdaTest platform.
  10. Playwright Python Tutorial: Playwright automation framework support all major languages such as Python, JavaScript, TypeScript, .NET and etc. However, there are various advantages to Python end-to-end testing with Playwright because of its versatile utility. Get the hang of Playwright python testing with this chapter.
  11. Playwright End To End Testing Tutorial: Get your hands on with Playwright end-to-end testing and learn to use some exciting features such as TraceViewer, Debugging, Networking, Component testing, Visual testing, and many more.
  12. Playwright Video Tutorial: Watch the video tutorials on Playwright testing from experts and get a consecutive in-depth explanation of Playwright automation testing.

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful