mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Initial hooks implementation
Includes: - useState - useContext - useEffect - useRef - useReducer - useCallback - useMemo - useAPI
This commit is contained in:
committed by
Andrew Clark
parent
37c7fe0a5f
commit
7bee9fbdd4
@@ -79,6 +79,7 @@ import {
|
||||
prepareToReadContext,
|
||||
calculateChangedBits,
|
||||
} from './ReactFiberNewContext';
|
||||
import {prepareToUseHooks, finishHooks, resetHooks} from './ReactFiberHooks';
|
||||
import {stopProfilerTimerIfRunning} from './ReactProfilerTimer';
|
||||
import {
|
||||
getMaskedContext,
|
||||
@@ -193,27 +194,17 @@ function forceUnmountCurrentAndReconcile(
|
||||
function updateForwardRef(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
type: any,
|
||||
Component: any,
|
||||
nextProps: any,
|
||||
renderExpirationTime: ExpirationTime,
|
||||
) {
|
||||
const render = type.render;
|
||||
const render = Component.render;
|
||||
const ref = workInProgress.ref;
|
||||
if (hasLegacyContextChanged()) {
|
||||
// Normally we can bail out on props equality but if context has changed
|
||||
// we don't do the bailout and we have to reuse existing props instead.
|
||||
} else if (workInProgress.memoizedProps === nextProps) {
|
||||
const currentRef = current !== null ? current.ref : null;
|
||||
if (ref === currentRef) {
|
||||
return bailoutOnAlreadyFinishedWork(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The rest is a fork of updateFunctionComponent
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
prepareToUseHooks(current, workInProgress, renderExpirationTime);
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
ReactCurrentFiber.setCurrentPhase('render');
|
||||
@@ -222,7 +213,10 @@ function updateForwardRef(
|
||||
} else {
|
||||
nextChildren = render(nextProps, ref);
|
||||
}
|
||||
nextChildren = finishHooks(render, nextProps, nextChildren, ref);
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
@@ -406,6 +400,7 @@ function updateFunctionComponent(
|
||||
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
prepareToUseHooks(current, workInProgress, renderExpirationTime);
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
ReactCurrentFiber.setCurrentPhase('render');
|
||||
@@ -414,6 +409,7 @@ function updateFunctionComponent(
|
||||
} else {
|
||||
nextChildren = Component(nextProps, context);
|
||||
}
|
||||
nextChildren = finishHooks(Component, nextProps, nextChildren, context);
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.effectTag |= PerformedWork;
|
||||
@@ -921,6 +917,7 @@ function mountIndeterminateComponent(
|
||||
const context = getMaskedContext(workInProgress, unmaskedContext);
|
||||
|
||||
prepareToReadContext(workInProgress, renderExpirationTime);
|
||||
prepareToUseHooks(null, workInProgress, renderExpirationTime);
|
||||
|
||||
let value;
|
||||
|
||||
@@ -964,6 +961,9 @@ function mountIndeterminateComponent(
|
||||
// Proceed under the assumption that this is a class instance
|
||||
workInProgress.tag = ClassComponent;
|
||||
|
||||
// Throw out any hooks that were used.
|
||||
resetHooks();
|
||||
|
||||
// Push context providers early to prevent context stack mismatches.
|
||||
// During mounting we don't know the child context yet as the instance doesn't exist.
|
||||
// We will invalidate the child context in finishClassComponent() right after rendering.
|
||||
@@ -1001,6 +1001,7 @@ function mountIndeterminateComponent(
|
||||
} else {
|
||||
// Proceed under the assumption that this is a function component
|
||||
workInProgress.tag = FunctionComponent;
|
||||
value = finishHooks(Component, props, value, context);
|
||||
if (__DEV__) {
|
||||
if (Component) {
|
||||
warningWithoutStack(
|
||||
|
||||
@@ -19,12 +19,15 @@ import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent';
|
||||
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
|
||||
|
||||
import {
|
||||
enableSchedulerTracing,
|
||||
enableProfilerTimer,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
FunctionComponent,
|
||||
ForwardRef,
|
||||
ClassComponent,
|
||||
HostRoot,
|
||||
HostComponent,
|
||||
@@ -180,6 +183,22 @@ function safelyDetachRef(current: Fiber) {
|
||||
}
|
||||
}
|
||||
|
||||
function safelyCallDestroy(current, destroy) {
|
||||
if (__DEV__) {
|
||||
invokeGuardedCallback(null, destroy, null);
|
||||
if (hasCaughtError()) {
|
||||
const error = clearCaughtError();
|
||||
captureCommitPhaseError(current, error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
destroy();
|
||||
} catch (error) {
|
||||
captureCommitPhaseError(current, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeMutationLifeCycles(
|
||||
current: Fiber | null,
|
||||
finishedWork: Fiber,
|
||||
@@ -235,6 +254,28 @@ function commitBeforeMutationLifeCycles(
|
||||
}
|
||||
}
|
||||
|
||||
function destroyRemainingEffects(firstToDestroy, stopAt) {
|
||||
let effect = firstToDestroy;
|
||||
do {
|
||||
const destroy = effect.value;
|
||||
if (destroy !== null) {
|
||||
destroy();
|
||||
}
|
||||
effect = effect.next;
|
||||
} while (effect !== stopAt);
|
||||
}
|
||||
|
||||
function destroyMountedEffects(current) {
|
||||
const oldUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
|
||||
if (oldUpdateQueue !== null) {
|
||||
const oldLastEffect = oldUpdateQueue.lastEffect;
|
||||
if (oldLastEffect !== null) {
|
||||
const oldFirstEffect = oldLastEffect.next;
|
||||
destroyRemainingEffects(oldFirstEffect, oldFirstEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function commitLifeCycles(
|
||||
finishedRoot: FiberRoot,
|
||||
current: Fiber | null,
|
||||
@@ -242,6 +283,116 @@ function commitLifeCycles(
|
||||
committedExpirationTime: ExpirationTime,
|
||||
): void {
|
||||
switch (finishedWork.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef: {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
|
||||
if (updateQueue !== null) {
|
||||
// Mount new effects and destroy the old ones by comparing to the
|
||||
// current list of effects. This could be a bit simpler if we avoided
|
||||
// the need to compare to the previous effect list by transferring the
|
||||
// old `destroy` method to the new effect during the render phase.
|
||||
// That's how I originally implemented it, but it requires an additional
|
||||
// field on the effect object.
|
||||
//
|
||||
// This supports removing effects from the end of the list. If we adopt
|
||||
// the constraint that hooks are append only, that would also save a bit
|
||||
// on code size.
|
||||
const newLastEffect = updateQueue.lastEffect;
|
||||
if (newLastEffect !== null) {
|
||||
const newFirstEffect = newLastEffect.next;
|
||||
let oldLastEffect = null;
|
||||
if (current !== null) {
|
||||
const oldUpdateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
|
||||
if (oldUpdateQueue !== null) {
|
||||
oldLastEffect = oldUpdateQueue.lastEffect;
|
||||
}
|
||||
}
|
||||
if (oldLastEffect !== null) {
|
||||
const oldFirstEffect = oldLastEffect.next;
|
||||
let newEffect = newFirstEffect;
|
||||
let oldEffect = oldFirstEffect;
|
||||
|
||||
// Before mounting the new effects, unmount all the old ones.
|
||||
do {
|
||||
if (oldEffect !== null) {
|
||||
if (newEffect.inputs !== oldEffect.inputs) {
|
||||
const destroy = oldEffect.value;
|
||||
if (destroy !== null) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
oldEffect = oldEffect.next;
|
||||
if (oldEffect === oldFirstEffect) {
|
||||
oldEffect = null;
|
||||
}
|
||||
}
|
||||
newEffect = newEffect.next;
|
||||
} while (newEffect !== newFirstEffect);
|
||||
|
||||
// Unmount any remaining effects in the old list that do not
|
||||
// appear in the new one.
|
||||
if (oldEffect !== null) {
|
||||
destroyRemainingEffects(oldEffect, oldFirstEffect);
|
||||
}
|
||||
|
||||
// Now loop through the list again to mount the new effects
|
||||
oldEffect = oldFirstEffect;
|
||||
do {
|
||||
const create = newEffect.value;
|
||||
if (oldEffect !== null) {
|
||||
if (newEffect.inputs !== oldEffect.inputs) {
|
||||
const newDestroy = create();
|
||||
newEffect.value =
|
||||
typeof newDestroy === 'function' ? newDestroy : null;
|
||||
} else {
|
||||
newEffect.value = oldEffect.value;
|
||||
}
|
||||
oldEffect = oldEffect.next;
|
||||
if (oldEffect === oldFirstEffect) {
|
||||
oldEffect = null;
|
||||
}
|
||||
} else {
|
||||
const newDestroy = create();
|
||||
newEffect.value =
|
||||
typeof newDestroy === 'function' ? newDestroy : null;
|
||||
}
|
||||
newEffect = newEffect.next;
|
||||
} while (newEffect !== newFirstEffect);
|
||||
} else {
|
||||
let newEffect = newFirstEffect;
|
||||
do {
|
||||
const create = newEffect.value;
|
||||
const newDestroy = create();
|
||||
newEffect.value =
|
||||
typeof newDestroy === 'function' ? newDestroy : null;
|
||||
newEffect = newEffect.next;
|
||||
} while (newEffect !== newFirstEffect);
|
||||
}
|
||||
} else if (current !== null) {
|
||||
// There are no effects, which means all current effects must
|
||||
// be destroyed
|
||||
destroyMountedEffects(current);
|
||||
}
|
||||
|
||||
const callbackList = updateQueue.callbackList;
|
||||
if (callbackList !== null) {
|
||||
updateQueue.callbackList = null;
|
||||
for (let i = 0; i < callbackList.length; i++) {
|
||||
const update = callbackList[i];
|
||||
// Assume this is non-null, since otherwise it would not be part
|
||||
// of the callback list.
|
||||
const callback: () => mixed = (update.callback: any);
|
||||
update.callback = null;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} else if (current !== null) {
|
||||
// There are no effects, which means all current effects must
|
||||
// be destroyed
|
||||
destroyMountedEffects(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClassComponent: {
|
||||
const instance = finishedWork.stateNode;
|
||||
if (finishedWork.effectTag & Update) {
|
||||
@@ -496,6 +647,25 @@ function commitUnmount(current: Fiber): void {
|
||||
onCommitUnmount(current);
|
||||
|
||||
switch (current.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef: {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
|
||||
if (updateQueue !== null) {
|
||||
const lastEffect = updateQueue.lastEffect;
|
||||
if (lastEffect !== null) {
|
||||
const firstEffect = lastEffect.next;
|
||||
let effect = firstEffect;
|
||||
do {
|
||||
const destroy = effect.value;
|
||||
if (destroy !== null) {
|
||||
safelyCallDestroy(current, destroy);
|
||||
}
|
||||
effect = effect.next;
|
||||
} while (effect !== firstEffect);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ClassComponent: {
|
||||
safelyDetachRef(current);
|
||||
const instance = current.stateNode;
|
||||
|
||||
@@ -8,7 +8,23 @@
|
||||
*/
|
||||
|
||||
import {readContext} from './ReactFiberNewContext';
|
||||
import {
|
||||
useState,
|
||||
useReducer,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useAPI,
|
||||
} from './ReactFiberHooks';
|
||||
|
||||
export const Dispatcher = {
|
||||
readContext,
|
||||
useState,
|
||||
useReducer,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useAPI,
|
||||
};
|
||||
|
||||
691
packages/react-reconciler/src/ReactFiberHooks.js
vendored
Normal file
691
packages/react-reconciler/src/ReactFiberHooks.js
vendored
Normal file
@@ -0,0 +1,691 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root direcreatey of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {Callback as CallbackEffect} from 'shared/ReactSideEffectTags';
|
||||
import {
|
||||
scheduleWork,
|
||||
computeExpirationForFiber,
|
||||
requestCurrentTime,
|
||||
} from './ReactFiberScheduler';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
type Update<S, A> = {
|
||||
expirationTime: ExpirationTime,
|
||||
action: A,
|
||||
callback: null | (S => mixed),
|
||||
next: Update<S, A> | null,
|
||||
};
|
||||
|
||||
type UpdateQueue<S, A> = {
|
||||
last: Update<S, A> | null,
|
||||
dispatch: any,
|
||||
};
|
||||
|
||||
type Hook = {
|
||||
memoizedState: any,
|
||||
|
||||
baseState: any,
|
||||
baseUpdate: Update<any, any> | null,
|
||||
queue: UpdateQueue<any, any> | null,
|
||||
|
||||
next: Hook | null,
|
||||
};
|
||||
|
||||
type Effect = {
|
||||
// For an unmounted effect, this points to the effect constructor. Once it's
|
||||
// mounted, it points to a destroy function (or null). I've opted to reuse
|
||||
// the same field to save memory.
|
||||
value: any,
|
||||
inputs: Array<mixed>,
|
||||
next: Effect,
|
||||
};
|
||||
|
||||
export type FunctionComponentUpdateQueue = {
|
||||
callbackList: Array<Update<any, any>> | null,
|
||||
lastEffect: Effect | null,
|
||||
};
|
||||
|
||||
type BasicStateAction<S> = S | (S => S);
|
||||
|
||||
type MaybeCallback<S> = void | null | (S => mixed);
|
||||
|
||||
type Dispatch<S, A> = (A, MaybeCallback<S>) => void;
|
||||
|
||||
// These are set right before calling the component.
|
||||
let renderExpirationTime: ExpirationTime = NoWork;
|
||||
// The work-in-progress fiber. I've named it differently to distinguish it from
|
||||
// the work-in-progress hook.
|
||||
let currentlyRenderingFiber: Fiber | null = null;
|
||||
|
||||
// Hooks are stored as a linked list on the fiber's memoizedState field. The
|
||||
// current hook list is the list that belongs to the current fiber. The
|
||||
// work-in-progress hook list is a new list that will be added to the
|
||||
// work-in-progress fiber.
|
||||
let firstCurrentHook: Hook | null = null;
|
||||
let currentHook: Hook | null = null;
|
||||
let firstWorkInProgressHook: Hook | null = null;
|
||||
let workInProgressHook: Hook | null = null;
|
||||
|
||||
let remainingExpirationTime: ExpirationTime = NoWork;
|
||||
let componentUpdateQueue: FunctionComponentUpdateQueue | null = null;
|
||||
|
||||
// Updates scheduled during render will trigger an immediate re-render at the
|
||||
// end of the current pass. We can't store these updates on the normal queue,
|
||||
// because if the work is aborted, they should be discarded. Because this is
|
||||
// a relatively rare case, we also don't want to add an additional field to
|
||||
// either the hook or queue object types. So we store them in a lazily create
|
||||
// map of queue -> render-phase updates, which are discarded once the component
|
||||
// completes without re-rendering.
|
||||
|
||||
// Whether the work-in-progress hook is a re-rendered hook
|
||||
let isReRender: boolean = false;
|
||||
// Whether an update was scheduled during the currently executing render pass.
|
||||
let didScheduleRenderPhaseUpdate: boolean = false;
|
||||
// Lazily created map of render-phase updates
|
||||
let renderPhaseUpdates: Map<
|
||||
UpdateQueue<any, any>,
|
||||
Update<any, any>,
|
||||
> | null = null;
|
||||
// Counter to prevent infinite loops.
|
||||
let numberOfReRenders: number = 0;
|
||||
const RE_RENDER_LIMIT = 25;
|
||||
|
||||
function resolveCurrentlyRenderingFiber(): Fiber {
|
||||
invariant(
|
||||
currentlyRenderingFiber !== null,
|
||||
'Hooks can only be called inside the body of a functional component.',
|
||||
);
|
||||
return currentlyRenderingFiber;
|
||||
}
|
||||
|
||||
export function prepareToUseHooks(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
nextRenderExpirationTime: ExpirationTime,
|
||||
): void {
|
||||
renderExpirationTime = nextRenderExpirationTime;
|
||||
currentlyRenderingFiber = workInProgress;
|
||||
firstCurrentHook = current !== null ? current.memoizedState : null;
|
||||
|
||||
// The following should have already been reset
|
||||
// currentHook = null;
|
||||
// workInProgressHook = null;
|
||||
|
||||
// remainingExpirationTime = NoWork;
|
||||
// componentUpdateQueue = null;
|
||||
|
||||
// isReRender = false;
|
||||
// didScheduleRenderPhaseUpdate = false;
|
||||
// renderPhaseUpdates = null;
|
||||
// numberOfReRenders = 0;
|
||||
}
|
||||
|
||||
export function finishHooks(
|
||||
Component: any,
|
||||
props: any,
|
||||
children: any,
|
||||
refOrContext: any,
|
||||
): any {
|
||||
// This must be called after every functional component to prevent hooks from
|
||||
// being used in classes.
|
||||
|
||||
while (didScheduleRenderPhaseUpdate) {
|
||||
// Updates were scheduled during the render phase. They are stored in
|
||||
// the `renderPhaseUpdates` map. Call the component again, reusing the
|
||||
// work-in-progress hooks and applying the additional updates on top. Keep
|
||||
// restarting until no more updates are scheduled.
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
numberOfReRenders += 1;
|
||||
|
||||
// Start over from the beginning of the list
|
||||
currentHook = null;
|
||||
workInProgressHook = null;
|
||||
componentUpdateQueue = null;
|
||||
|
||||
children = Component(props, refOrContext);
|
||||
}
|
||||
renderPhaseUpdates = null;
|
||||
numberOfReRenders = 0;
|
||||
|
||||
const renderedWork: Fiber = (currentlyRenderingFiber: any);
|
||||
|
||||
renderedWork.memoizedState = firstWorkInProgressHook;
|
||||
renderedWork.expirationTime = remainingExpirationTime;
|
||||
if (componentUpdateQueue !== null) {
|
||||
renderedWork.updateQueue = (componentUpdateQueue: any);
|
||||
}
|
||||
|
||||
renderExpirationTime = NoWork;
|
||||
currentlyRenderingFiber = null;
|
||||
|
||||
firstCurrentHook = null;
|
||||
currentHook = null;
|
||||
firstWorkInProgressHook = null;
|
||||
workInProgressHook = null;
|
||||
|
||||
remainingExpirationTime = NoWork;
|
||||
componentUpdateQueue = null;
|
||||
|
||||
// Always set during createWorkInProgress
|
||||
// isReRender = false;
|
||||
|
||||
// These were reset above
|
||||
// didScheduleRenderPhaseUpdate = false;
|
||||
// renderPhaseUpdates = null;
|
||||
// numberOfReRenders = 0;
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export function resetHooks(): void {
|
||||
// This is called instead of `finishHooks` if the component throws. It's also
|
||||
// called inside mountIndeterminateComponent if we determine the component
|
||||
// is a module-style component.
|
||||
renderExpirationTime = NoWork;
|
||||
currentlyRenderingFiber = null;
|
||||
|
||||
firstCurrentHook = null;
|
||||
currentHook = null;
|
||||
firstWorkInProgressHook = null;
|
||||
workInProgressHook = null;
|
||||
|
||||
remainingExpirationTime = NoWork;
|
||||
componentUpdateQueue = null;
|
||||
|
||||
// Always set during createWorkInProgress
|
||||
// isReRender = false;
|
||||
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
renderPhaseUpdates = null;
|
||||
numberOfReRenders = 0;
|
||||
}
|
||||
|
||||
function createHook(): Hook {
|
||||
return {
|
||||
memoizedState: null,
|
||||
|
||||
baseState: null,
|
||||
queue: null,
|
||||
baseUpdate: null,
|
||||
|
||||
next: null,
|
||||
};
|
||||
}
|
||||
|
||||
function cloneHook(hook: Hook): Hook {
|
||||
return {
|
||||
memoizedState: hook.memoizedState,
|
||||
|
||||
baseState: hook.memoizedState,
|
||||
queue: hook.queue,
|
||||
baseUpdate: hook.baseUpdate,
|
||||
|
||||
next: null,
|
||||
};
|
||||
}
|
||||
|
||||
function createWorkInProgressHook(): Hook {
|
||||
if (workInProgressHook === null) {
|
||||
// This is the first hook in the list
|
||||
if (firstWorkInProgressHook === null) {
|
||||
isReRender = false;
|
||||
currentHook = firstCurrentHook;
|
||||
if (currentHook === null) {
|
||||
// This is a newly mounted hook
|
||||
workInProgressHook = createHook();
|
||||
} else {
|
||||
// Clone the current hook.
|
||||
workInProgressHook = cloneHook(currentHook);
|
||||
}
|
||||
firstWorkInProgressHook = workInProgressHook;
|
||||
} else {
|
||||
// There's already a work-in-progress. Reuse it.
|
||||
isReRender = true;
|
||||
currentHook = firstCurrentHook;
|
||||
workInProgressHook = firstWorkInProgressHook;
|
||||
}
|
||||
} else {
|
||||
if (workInProgressHook.next === null) {
|
||||
isReRender = false;
|
||||
let hook;
|
||||
if (currentHook === null) {
|
||||
// This is a newly mounted hook
|
||||
hook = createHook();
|
||||
} else {
|
||||
currentHook = currentHook.next;
|
||||
if (currentHook === null) {
|
||||
// This is a newly mounted hook
|
||||
hook = createHook();
|
||||
} else {
|
||||
// Clone the current hook.
|
||||
hook = cloneHook(currentHook);
|
||||
}
|
||||
}
|
||||
// Append to the end of the list
|
||||
workInProgressHook = workInProgressHook.next = hook;
|
||||
} else {
|
||||
// There's already a work-in-progress. Reuse it.
|
||||
isReRender = true;
|
||||
workInProgressHook = workInProgressHook.next;
|
||||
currentHook = currentHook !== null ? currentHook.next : null;
|
||||
}
|
||||
}
|
||||
return workInProgressHook;
|
||||
}
|
||||
|
||||
function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
|
||||
return {
|
||||
callbackList: null,
|
||||
lastEffect: null,
|
||||
};
|
||||
}
|
||||
|
||||
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
|
||||
return typeof action === 'function' ? action(state) : action;
|
||||
}
|
||||
|
||||
export function useState<S>(
|
||||
initialState: S | (() => S),
|
||||
): [S, Dispatch<S, BasicStateAction<S>>] {
|
||||
return useReducer(
|
||||
basicStateReducer,
|
||||
// useReducer has a special case to support lazy useState initializers
|
||||
(initialState: any),
|
||||
);
|
||||
}
|
||||
|
||||
export function useReducer<S, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialState: S,
|
||||
initialAction: A | void | null,
|
||||
): [S, Dispatch<S, A>] {
|
||||
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
if (isReRender) {
|
||||
// This is a re-render. Apply the new render phase updates to the previous
|
||||
// work-in-progress hook.
|
||||
const queue: UpdateQueue<S, A> = (workInProgressHook.queue: any);
|
||||
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
|
||||
if (renderPhaseUpdates !== null) {
|
||||
// Render phase updates are stored in a map of queue -> linked list
|
||||
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
|
||||
if (firstRenderPhaseUpdate !== undefined) {
|
||||
renderPhaseUpdates.delete(queue);
|
||||
let newState = workInProgressHook.memoizedState;
|
||||
let update = firstRenderPhaseUpdate;
|
||||
do {
|
||||
// Process this render phase update. We don't have to check the
|
||||
// priority because it will always be the same as the current
|
||||
// render's.
|
||||
const action = update.action;
|
||||
newState = reducer(newState, action);
|
||||
const callback = update.callback;
|
||||
if (callback !== null) {
|
||||
pushCallback(currentlyRenderingFiber, update);
|
||||
}
|
||||
update = update.next;
|
||||
} while (update !== null);
|
||||
|
||||
workInProgressHook.memoizedState = newState;
|
||||
|
||||
// Don't persist the state accumlated from the render phase updates to
|
||||
// the base state unless the queue is empty.
|
||||
// TODO: Not sure if this is the desired semantics, but it's what we
|
||||
// do for gDSFP. I can't remember why.
|
||||
if (workInProgressHook.baseUpdate === queue.last) {
|
||||
workInProgressHook.baseState = newState;
|
||||
}
|
||||
|
||||
return [newState, dispatch];
|
||||
}
|
||||
}
|
||||
return [workInProgressHook.memoizedState, dispatch];
|
||||
} else if (currentHook !== null) {
|
||||
const queue: UpdateQueue<S, A> = (workInProgressHook.queue: any);
|
||||
|
||||
// The last update in the entire queue
|
||||
const last = queue.last;
|
||||
// The last update that is part of the base state.
|
||||
const baseUpdate = workInProgressHook.baseUpdate;
|
||||
|
||||
// Find the first unprocessed update.
|
||||
let first;
|
||||
if (baseUpdate !== null) {
|
||||
if (last !== null) {
|
||||
// For the first update, the queue is a circular linked list where
|
||||
// `queue.last.next = queue.first`. Once the first update commits, and
|
||||
// the `baseUpdate` is no longer empty, we can unravel the list.
|
||||
last.next = null;
|
||||
}
|
||||
first = baseUpdate.next;
|
||||
} else {
|
||||
first = last !== null ? last.next : null;
|
||||
}
|
||||
if (first !== null) {
|
||||
let newState = workInProgressHook.baseState;
|
||||
let newBaseState = null;
|
||||
let newBaseUpdate = null;
|
||||
let prevUpdate = baseUpdate;
|
||||
let update = first;
|
||||
let didSkip = false;
|
||||
do {
|
||||
const updateExpirationTime = update.expirationTime;
|
||||
if (updateExpirationTime > renderExpirationTime) {
|
||||
// Priority is insufficient. Skip this update. If this is the first
|
||||
// skipped update, the previous update/state is the new base
|
||||
// update/state.
|
||||
if (!didSkip) {
|
||||
didSkip = true;
|
||||
newBaseUpdate = prevUpdate;
|
||||
newBaseState = newState;
|
||||
}
|
||||
// Update the remaining priority in the queue.
|
||||
if (
|
||||
remainingExpirationTime === NoWork ||
|
||||
updateExpirationTime < remainingExpirationTime
|
||||
) {
|
||||
remainingExpirationTime = updateExpirationTime;
|
||||
}
|
||||
} else {
|
||||
// Process this update.
|
||||
const action = update.action;
|
||||
newState = reducer(newState, action);
|
||||
const callback = update.callback;
|
||||
if (callback !== null) {
|
||||
pushCallback(currentlyRenderingFiber, update);
|
||||
}
|
||||
}
|
||||
prevUpdate = update;
|
||||
update = update.next;
|
||||
} while (update !== null && update !== first);
|
||||
|
||||
if (!didSkip) {
|
||||
newBaseUpdate = prevUpdate;
|
||||
newBaseState = newState;
|
||||
}
|
||||
|
||||
workInProgressHook.memoizedState = newState;
|
||||
workInProgressHook.baseUpdate = newBaseUpdate;
|
||||
workInProgressHook.baseState = newBaseState;
|
||||
}
|
||||
|
||||
const dispatch: Dispatch<S, A> = (queue.dispatch: any);
|
||||
return [workInProgressHook.memoizedState, dispatch];
|
||||
} else {
|
||||
if (reducer === basicStateReducer) {
|
||||
// Special case for `useState`.
|
||||
if (typeof initialState === 'function') {
|
||||
initialState = initialState();
|
||||
}
|
||||
} else if (initialAction !== undefined && initialAction !== null) {
|
||||
initialState = reducer(initialState, initialAction);
|
||||
}
|
||||
workInProgressHook.memoizedState = workInProgressHook.baseState = initialState;
|
||||
const queue: UpdateQueue<S, A> = (workInProgressHook.queue = {
|
||||
last: null,
|
||||
dispatch: null,
|
||||
});
|
||||
const dispatch: Dispatch<S, A> = (queue.dispatch = (dispatchAction.bind(
|
||||
null,
|
||||
currentlyRenderingFiber,
|
||||
queue,
|
||||
): any));
|
||||
return [workInProgressHook.memoizedState, dispatch];
|
||||
}
|
||||
}
|
||||
|
||||
function pushCallback(workInProgress: Fiber, update: Update<any, any>): void {
|
||||
if (componentUpdateQueue === null) {
|
||||
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
||||
componentUpdateQueue.callbackList = [update];
|
||||
} else {
|
||||
const callbackList = componentUpdateQueue.callbackList;
|
||||
if (callbackList === null) {
|
||||
componentUpdateQueue.callbackList = [update];
|
||||
} else {
|
||||
callbackList.push(update);
|
||||
}
|
||||
}
|
||||
workInProgress.effectTag |= CallbackEffect;
|
||||
}
|
||||
|
||||
function pushEffect(value, inputs) {
|
||||
const effect: Effect = {
|
||||
value,
|
||||
inputs,
|
||||
// Circular
|
||||
next: (null: any),
|
||||
};
|
||||
if (componentUpdateQueue === null) {
|
||||
componentUpdateQueue = createFunctionComponentUpdateQueue();
|
||||
componentUpdateQueue.lastEffect = effect.next = effect;
|
||||
} else {
|
||||
const lastEffect = componentUpdateQueue.lastEffect;
|
||||
if (lastEffect === null) {
|
||||
componentUpdateQueue.lastEffect = effect.next = effect;
|
||||
} else {
|
||||
const firstEffect = lastEffect.next;
|
||||
lastEffect.next = effect;
|
||||
effect.next = firstEffect;
|
||||
componentUpdateQueue.lastEffect = effect;
|
||||
}
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
export function useRef<T>(initialValue: T): {current: T} {
|
||||
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
let ref;
|
||||
if (currentHook === null) {
|
||||
ref = {current: initialValue};
|
||||
if (__DEV__) {
|
||||
Object.seal(ref);
|
||||
}
|
||||
workInProgressHook.memoizedState = ref;
|
||||
} else {
|
||||
ref = workInProgressHook.memoizedState;
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
export function useEffect(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
|
||||
let nextEffect;
|
||||
let nextInputs = inputs !== undefined && inputs !== null ? inputs : [create];
|
||||
if (currentHook !== null) {
|
||||
const prevEffect = currentHook.memoizedState;
|
||||
const prevInputs = prevEffect.inputs;
|
||||
if (inputsAreEqual(nextInputs, prevInputs)) {
|
||||
nextEffect = pushEffect(prevEffect.value, prevInputs);
|
||||
} else {
|
||||
nextEffect = pushEffect(create, nextInputs);
|
||||
}
|
||||
} else {
|
||||
nextEffect = pushEffect(create, nextInputs);
|
||||
}
|
||||
|
||||
// TODO: If we decide not to support removing hooks from the end of the list,
|
||||
// we only need to schedule an effect if the inputs changed.
|
||||
currentlyRenderingFiber.effectTag |= CallbackEffect;
|
||||
workInProgressHook.memoizedState = nextEffect;
|
||||
}
|
||||
|
||||
export function useAPI<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
// TODO: If inputs are provided, should we skip comparing the ref itself?
|
||||
const nextInputs =
|
||||
inputs !== null && inputs !== undefined
|
||||
? inputs.concat([ref])
|
||||
: [ref, create];
|
||||
|
||||
// TODO: I've implemented this on top of useEffect because it's almost the
|
||||
// same thing, and it would require an equal amount of code. It doesn't seem
|
||||
// like a common enough use case to justify the additional size.
|
||||
useEffect(() => {
|
||||
if (typeof ref === 'function') {
|
||||
const refCallback = ref;
|
||||
const inst = create();
|
||||
refCallback(inst);
|
||||
return () => refCallback(null);
|
||||
} else if (ref !== null && ref !== undefined) {
|
||||
const refObject = ref;
|
||||
const inst = create();
|
||||
refObject.current = inst;
|
||||
return () => {
|
||||
refObject.current = null;
|
||||
};
|
||||
}
|
||||
}, nextInputs);
|
||||
}
|
||||
|
||||
export function useCallback<T>(
|
||||
callback: T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): T {
|
||||
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
|
||||
const nextInputs =
|
||||
inputs !== undefined && inputs !== null ? inputs : [callback];
|
||||
|
||||
if (currentHook !== null) {
|
||||
const prevState = currentHook.memoizedState;
|
||||
const prevInputs = prevState[1];
|
||||
if (inputsAreEqual(nextInputs, prevInputs)) {
|
||||
return prevState[0];
|
||||
}
|
||||
}
|
||||
|
||||
workInProgressHook.memoizedState = [callback, nextInputs];
|
||||
return callback;
|
||||
}
|
||||
|
||||
export function useMemo<T>(
|
||||
nextCreate: () => T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): T {
|
||||
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
|
||||
const nextInputs =
|
||||
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
|
||||
|
||||
if (currentHook !== null) {
|
||||
const prevState = currentHook.memoizedState;
|
||||
const prevInputs = prevState[1];
|
||||
if (inputsAreEqual(nextInputs, prevInputs)) {
|
||||
return prevState[0];
|
||||
}
|
||||
}
|
||||
|
||||
const nextValue = nextCreate();
|
||||
workInProgressHook.memoizedState = [nextValue, nextInputs];
|
||||
return nextValue;
|
||||
}
|
||||
|
||||
function dispatchAction<S, A>(
|
||||
fiber: Fiber,
|
||||
queue: UpdateQueue<S, A>,
|
||||
action: A,
|
||||
callback: void | null | (S => mixed),
|
||||
) {
|
||||
invariant(
|
||||
numberOfReRenders < RE_RENDER_LIMIT,
|
||||
'Too many re-renders. React limits the number of renders to prevent ' +
|
||||
'an infinite loop.',
|
||||
);
|
||||
|
||||
const alternate = fiber.alternate;
|
||||
if (
|
||||
fiber === currentlyRenderingFiber ||
|
||||
(alternate !== null && alternate === currentlyRenderingFiber)
|
||||
) {
|
||||
// This is a render phase update. Stash it in a lazily-created map of
|
||||
// queue -> linked list of updates. After this render pass, we'll restart
|
||||
// and apply the stashed updates on top of the work-in-progress hook.
|
||||
didScheduleRenderPhaseUpdate = true;
|
||||
const update: Update<S, A> = {
|
||||
expirationTime: renderExpirationTime,
|
||||
action,
|
||||
callback: callback !== undefined ? callback : null,
|
||||
next: null,
|
||||
};
|
||||
if (renderPhaseUpdates === null) {
|
||||
renderPhaseUpdates = new Map();
|
||||
}
|
||||
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
|
||||
if (firstRenderPhaseUpdate === undefined) {
|
||||
renderPhaseUpdates.set(queue, update);
|
||||
} else {
|
||||
// Append the update to the end of the list.
|
||||
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
|
||||
while (lastRenderPhaseUpdate.next !== null) {
|
||||
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
|
||||
}
|
||||
lastRenderPhaseUpdate.next = update;
|
||||
}
|
||||
} else {
|
||||
const currentTime = requestCurrentTime();
|
||||
const expirationTime = computeExpirationForFiber(currentTime, fiber);
|
||||
const update: Update<S, A> = {
|
||||
expirationTime,
|
||||
action,
|
||||
callback: callback !== undefined ? callback : null,
|
||||
next: null,
|
||||
};
|
||||
// Append the update to the end of the list.
|
||||
const last = queue.last;
|
||||
if (last === null) {
|
||||
// This is the first update. Create a circular list.
|
||||
update.next = update;
|
||||
} else {
|
||||
const first = last.next;
|
||||
if (first !== null) {
|
||||
// Still circular.
|
||||
update.next = first;
|
||||
}
|
||||
last.next = update;
|
||||
}
|
||||
queue.last = update;
|
||||
scheduleWork(fiber, expirationTime);
|
||||
}
|
||||
}
|
||||
|
||||
function inputsAreEqual(arr1, arr2) {
|
||||
// Don't bother comparing lengths because these arrays are always
|
||||
// passed inline.
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
// Inlined Object.is polyfill.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
||||
const val1 = arr1[i];
|
||||
const val2 = arr2[i];
|
||||
if (
|
||||
(val1 === val2 && (val1 !== 0 || 1 / val1 === 1 / (val2: any))) ||
|
||||
(val1 !== val1 && val2 !== val2) // eslint-disable-line no-self-compare
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -122,6 +122,7 @@ import {
|
||||
popContext as popLegacyContext,
|
||||
} from './ReactFiberContext';
|
||||
import {popProvider, resetContextDependences} from './ReactFiberNewContext';
|
||||
import {resetHooks} from './ReactFiberHooks';
|
||||
import {popHostContext, popHostContainer} from './ReactFiberHostContext';
|
||||
import {
|
||||
recordCommitTime,
|
||||
@@ -1222,6 +1223,9 @@ function renderRoot(
|
||||
try {
|
||||
workLoop(isYieldy);
|
||||
} catch (thrownValue) {
|
||||
resetContextDependences();
|
||||
resetHooks();
|
||||
|
||||
if (nextUnitOfWork === null) {
|
||||
// This is a fatal error.
|
||||
didFatal = true;
|
||||
@@ -1284,6 +1288,7 @@ function renderRoot(
|
||||
isWorking = false;
|
||||
ReactCurrentOwner.currentDispatcher = null;
|
||||
resetContextDependences();
|
||||
resetHooks();
|
||||
|
||||
// Yield back to main thread.
|
||||
if (didFatal) {
|
||||
|
||||
1064
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
Normal file
1064
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
let ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
|
||||
let React = require('react');
|
||||
let useContext;
|
||||
let ReactNoop;
|
||||
let gen;
|
||||
|
||||
@@ -21,6 +22,7 @@ describe('ReactNewContext', () => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
useContext = React.useContext;
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
gen = require('random-seed');
|
||||
});
|
||||
@@ -34,33 +36,26 @@ describe('ReactNewContext', () => {
|
||||
return {type: 'span', children: [], prop, hidden: false};
|
||||
}
|
||||
|
||||
function readContext(Context, observedBits) {
|
||||
const dispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner
|
||||
.currentDispatcher;
|
||||
return dispatcher.readContext(Context, observedBits);
|
||||
}
|
||||
|
||||
// We have several ways of reading from context. sharedContextTests runs
|
||||
// a suite of tests for a given context consumer implementation.
|
||||
sharedContextTests('Context.Consumer', Context => Context.Consumer);
|
||||
sharedContextTests(
|
||||
'readContext(Context) inside function component',
|
||||
'useContext inside functional component',
|
||||
Context =>
|
||||
function Consumer(props) {
|
||||
const observedBits = props.unstable_observedBits;
|
||||
const contextValue = readContext(Context, observedBits);
|
||||
const contextValue = useContext(Context, observedBits);
|
||||
const render = props.children;
|
||||
return render(contextValue);
|
||||
},
|
||||
);
|
||||
sharedContextTests(
|
||||
'readContext(Context) inside class component',
|
||||
'useContext inside class component',
|
||||
Context =>
|
||||
class Consumer extends React.Component {
|
||||
render() {
|
||||
const observedBits = this.props.unstable_observedBits;
|
||||
const contextValue = readContext(Context, observedBits);
|
||||
const contextValue = useContext(Context, observedBits);
|
||||
const render = this.props.children;
|
||||
return render(contextValue);
|
||||
}
|
||||
@@ -1194,7 +1189,7 @@ describe('ReactNewContext', () => {
|
||||
return (
|
||||
<FooContext.Consumer>
|
||||
{foo => {
|
||||
const bar = readContext(BarContext);
|
||||
const bar = useContext(BarContext);
|
||||
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
|
||||
}}
|
||||
</FooContext.Consumer>
|
||||
@@ -1238,7 +1233,7 @@ describe('ReactNewContext', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('readContext', () => {
|
||||
describe('useContext', () => {
|
||||
it('can use the same context multiple times in the same function', () => {
|
||||
const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
|
||||
let result = 0;
|
||||
@@ -1264,13 +1259,13 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
function FooAndBar() {
|
||||
const {foo} = readContext(Context, 0b001);
|
||||
const {bar} = readContext(Context, 0b010);
|
||||
const {foo} = useContext(Context, 0b001);
|
||||
const {bar} = useContext(Context, 0b010);
|
||||
return <Text text={`Foo: ${foo}, Bar: ${bar}`} />;
|
||||
}
|
||||
|
||||
function Baz() {
|
||||
const {baz} = readContext(Context, 0b100);
|
||||
const {baz} = useContext(Context, 0b100);
|
||||
return <Text text={'Baz: ' + baz} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@ import {createContext} from './ReactContext';
|
||||
import {lazy} from './ReactLazy';
|
||||
import forwardRef from './forwardRef';
|
||||
import memo from './memo';
|
||||
import {
|
||||
useContext,
|
||||
useState,
|
||||
useReducer,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useAPI,
|
||||
} from './ReactHooks';
|
||||
import {
|
||||
createElementWithValidation,
|
||||
createFactoryWithValidation,
|
||||
@@ -53,6 +63,15 @@ const React = {
|
||||
lazy,
|
||||
memo,
|
||||
|
||||
useContext,
|
||||
useState,
|
||||
useReducer,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useAPI,
|
||||
|
||||
Fragment: REACT_FRAGMENT_TYPE,
|
||||
StrictMode: REACT_STRICT_MODE_TYPE,
|
||||
Suspense: REACT_SUSPENSE_TYPE,
|
||||
|
||||
83
packages/react/src/ReactHooks.js
Normal file
83
packages/react/src/ReactHooks.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactContext} from 'shared/ReactTypes';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import ReactCurrentOwner from './ReactCurrentOwner';
|
||||
|
||||
function resolveDispatcher() {
|
||||
const dispatcher = ReactCurrentOwner.currentDispatcher;
|
||||
invariant(
|
||||
dispatcher !== null,
|
||||
'Hooks can only be called inside the body of a functional component.',
|
||||
);
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
export function useContext<T>(
|
||||
Context: ReactContext<T>,
|
||||
observedBits: number | boolean | void,
|
||||
) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.readContext(Context, observedBits);
|
||||
}
|
||||
|
||||
export function useState<S>(initialState: S | (() => S)) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useState(initialState);
|
||||
}
|
||||
|
||||
export function useReducer<S, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialState: S,
|
||||
initialAction: A | void | null,
|
||||
) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useReducer(reducer, initialState, initialAction);
|
||||
}
|
||||
|
||||
export function useRef<T>(initialValue: T): {current: T} {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useRef(initialValue);
|
||||
}
|
||||
|
||||
export function useEffect(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useEffect(create, inputs);
|
||||
}
|
||||
|
||||
export function useCallback(
|
||||
callback: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useCallback(callback, inputs);
|
||||
}
|
||||
|
||||
export function useMemo(
|
||||
create: () => mixed,
|
||||
inputs: Array<mixed> | void | null,
|
||||
) {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useMemo(create, inputs);
|
||||
}
|
||||
|
||||
export function useAPI<T>(
|
||||
ref: {current: T | null} | ((inst: T | null) => mixed) | null | void,
|
||||
create: () => T,
|
||||
inputs: Array<mixed> | void | null,
|
||||
): void {
|
||||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useAPI(ref, create, inputs);
|
||||
}
|
||||
Reference in New Issue
Block a user