mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fizz] Implement Hooks (#21257)
* Implement Fizz Hooks This is pretty much just a copy of the partial renderer Hooks. * Implement forward ref and memo
This commit is contained in:
+515
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* 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 {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import type {
|
||||
MutableSource,
|
||||
MutableSourceGetSnapshotFn,
|
||||
MutableSourceSubscribeFn,
|
||||
ReactContext,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import {readContext as readContextImpl} from './ReactFizzNewContext';
|
||||
|
||||
import invariant from 'shared/invariant';
|
||||
import {enableCache} from 'shared/ReactFeatureFlags';
|
||||
import is from 'shared/objectIs';
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
type Dispatch<A> = A => void;
|
||||
|
||||
type Update<A> = {|
|
||||
action: A,
|
||||
next: Update<A> | null,
|
||||
|};
|
||||
|
||||
type UpdateQueue<A> = {|
|
||||
last: Update<A> | null,
|
||||
dispatch: any,
|
||||
|};
|
||||
|
||||
type Hook = {|
|
||||
memoizedState: any,
|
||||
queue: UpdateQueue<any> | null,
|
||||
next: Hook | null,
|
||||
|};
|
||||
|
||||
type OpaqueIDType = string;
|
||||
|
||||
let currentlyRenderingComponent: Object | null = null;
|
||||
let firstWorkInProgressHook: Hook | null = null;
|
||||
let workInProgressHook: Hook | null = null;
|
||||
// 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>, Update<any>> | null = null;
|
||||
// Counter to prevent infinite loops.
|
||||
let numberOfReRenders: number = 0;
|
||||
const RE_RENDER_LIMIT = 25;
|
||||
|
||||
let isInHookUserCodeInDev = false;
|
||||
|
||||
// In DEV, this is the name of the currently executing primitive hook
|
||||
let currentHookNameInDev: ?string;
|
||||
|
||||
function resolveCurrentlyRenderingComponent(): Object {
|
||||
invariant(
|
||||
currentlyRenderingComponent !== null,
|
||||
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
|
||||
' one of the following reasons:\n' +
|
||||
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
|
||||
'2. You might be breaking the Rules of Hooks\n' +
|
||||
'3. You might have more than one copy of React in the same app\n' +
|
||||
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
|
||||
);
|
||||
if (__DEV__) {
|
||||
if (isInHookUserCodeInDev) {
|
||||
console.error(
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
|
||||
'You can only call Hooks at the top level of your React function. ' +
|
||||
'For more information, see ' +
|
||||
'https://reactjs.org/link/rules-of-hooks',
|
||||
);
|
||||
}
|
||||
}
|
||||
return currentlyRenderingComponent;
|
||||
}
|
||||
|
||||
function areHookInputsEqual(
|
||||
nextDeps: Array<mixed>,
|
||||
prevDeps: Array<mixed> | null,
|
||||
) {
|
||||
if (prevDeps === null) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'%s received a final argument during this render, but not during ' +
|
||||
'the previous render. Even though the final argument is optional, ' +
|
||||
'its type cannot change between renders.',
|
||||
currentHookNameInDev,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// Don't bother comparing lengths in prod because these arrays should be
|
||||
// passed inline.
|
||||
if (nextDeps.length !== prevDeps.length) {
|
||||
console.error(
|
||||
'The final argument passed to %s changed size between renders. The ' +
|
||||
'order and size of this array must remain constant.\n\n' +
|
||||
'Previous: %s\n' +
|
||||
'Incoming: %s',
|
||||
currentHookNameInDev,
|
||||
`[${nextDeps.join(', ')}]`,
|
||||
`[${prevDeps.join(', ')}]`,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
|
||||
if (is(nextDeps[i], prevDeps[i])) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createHook(): Hook {
|
||||
if (numberOfReRenders > 0) {
|
||||
invariant(false, 'Rendered more hooks than during the previous render');
|
||||
}
|
||||
return {
|
||||
memoizedState: null,
|
||||
queue: null,
|
||||
next: null,
|
||||
};
|
||||
}
|
||||
|
||||
function createWorkInProgressHook(): Hook {
|
||||
if (workInProgressHook === null) {
|
||||
// This is the first hook in the list
|
||||
if (firstWorkInProgressHook === null) {
|
||||
isReRender = false;
|
||||
firstWorkInProgressHook = workInProgressHook = createHook();
|
||||
} else {
|
||||
// There's already a work-in-progress. Reuse it.
|
||||
isReRender = true;
|
||||
workInProgressHook = firstWorkInProgressHook;
|
||||
}
|
||||
} else {
|
||||
if (workInProgressHook.next === null) {
|
||||
isReRender = false;
|
||||
// Append to the end of the list
|
||||
workInProgressHook = workInProgressHook.next = createHook();
|
||||
} else {
|
||||
// There's already a work-in-progress. Reuse it.
|
||||
isReRender = true;
|
||||
workInProgressHook = workInProgressHook.next;
|
||||
}
|
||||
}
|
||||
return workInProgressHook;
|
||||
}
|
||||
|
||||
export function prepareToUseHooks(componentIdentity: Object): void {
|
||||
currentlyRenderingComponent = componentIdentity;
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = false;
|
||||
}
|
||||
|
||||
// The following should have already been reset
|
||||
// didScheduleRenderPhaseUpdate = false;
|
||||
// firstWorkInProgressHook = null;
|
||||
// numberOfReRenders = 0;
|
||||
// renderPhaseUpdates = null;
|
||||
// workInProgressHook = null;
|
||||
}
|
||||
|
||||
export function finishHooks(
|
||||
Component: any,
|
||||
props: any,
|
||||
children: any,
|
||||
refOrContext: any,
|
||||
): any {
|
||||
// This must be called after every function 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
|
||||
workInProgressHook = null;
|
||||
|
||||
children = Component(props, refOrContext);
|
||||
}
|
||||
resetHooksState();
|
||||
return children;
|
||||
}
|
||||
|
||||
// Reset the internal hooks state if an error occurs while rendering a component
|
||||
export function resetHooksState(): void {
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = false;
|
||||
}
|
||||
|
||||
currentlyRenderingComponent = null;
|
||||
didScheduleRenderPhaseUpdate = false;
|
||||
firstWorkInProgressHook = null;
|
||||
numberOfReRenders = 0;
|
||||
renderPhaseUpdates = null;
|
||||
workInProgressHook = null;
|
||||
}
|
||||
|
||||
function getCacheForType<T>(resourceType: () => T): T {
|
||||
// TODO: This should silently mark this as client rendered since it's not necesssarily
|
||||
// considered an error. It needs to work for things like Flight though.
|
||||
invariant(false, 'Not implemented.');
|
||||
}
|
||||
|
||||
function readContext<T>(context: ReactContext<T>): T {
|
||||
if (__DEV__) {
|
||||
if (isInHookUserCodeInDev) {
|
||||
console.error(
|
||||
'Context can only be read while React is rendering. ' +
|
||||
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
|
||||
'In function components, you can read it directly in the function body, but not ' +
|
||||
'inside Hooks like useReducer() or useMemo().',
|
||||
);
|
||||
}
|
||||
}
|
||||
return readContextImpl(context);
|
||||
}
|
||||
|
||||
function useContext<T>(context: ReactContext<T>): T {
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = 'useContext';
|
||||
}
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return readContextImpl(context);
|
||||
}
|
||||
|
||||
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
|
||||
// $FlowFixMe: Flow doesn't like mixed types
|
||||
return typeof action === 'function' ? action(state) : action;
|
||||
}
|
||||
|
||||
export function useState<S>(
|
||||
initialState: (() => S) | S,
|
||||
): [S, Dispatch<BasicStateAction<S>>] {
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = 'useState';
|
||||
}
|
||||
return useReducer(
|
||||
basicStateReducer,
|
||||
// useReducer has a special case to support lazy useState initializers
|
||||
(initialState: any),
|
||||
);
|
||||
}
|
||||
|
||||
export function useReducer<S, I, A>(
|
||||
reducer: (S, A) => S,
|
||||
initialArg: I,
|
||||
init?: I => S,
|
||||
): [S, Dispatch<A>] {
|
||||
if (__DEV__) {
|
||||
if (reducer !== basicStateReducer) {
|
||||
currentHookNameInDev = 'useReducer';
|
||||
}
|
||||
}
|
||||
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
if (isReRender) {
|
||||
// This is a re-render. Apply the new render phase updates to the previous
|
||||
// current hook.
|
||||
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
|
||||
const dispatch: Dispatch<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;
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = true;
|
||||
}
|
||||
newState = reducer(newState, action);
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = false;
|
||||
}
|
||||
update = update.next;
|
||||
} while (update !== null);
|
||||
|
||||
workInProgressHook.memoizedState = newState;
|
||||
|
||||
return [newState, dispatch];
|
||||
}
|
||||
}
|
||||
return [workInProgressHook.memoizedState, dispatch];
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = true;
|
||||
}
|
||||
let initialState;
|
||||
if (reducer === basicStateReducer) {
|
||||
// Special case for `useState`.
|
||||
initialState =
|
||||
typeof initialArg === 'function'
|
||||
? ((initialArg: any): () => S)()
|
||||
: ((initialArg: any): S);
|
||||
} else {
|
||||
initialState =
|
||||
init !== undefined ? init(initialArg) : ((initialArg: any): S);
|
||||
}
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = false;
|
||||
}
|
||||
workInProgressHook.memoizedState = initialState;
|
||||
const queue: UpdateQueue<A> = (workInProgressHook.queue = {
|
||||
last: null,
|
||||
dispatch: null,
|
||||
});
|
||||
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
|
||||
null,
|
||||
currentlyRenderingComponent,
|
||||
queue,
|
||||
): any));
|
||||
return [workInProgressHook.memoizedState, dispatch];
|
||||
}
|
||||
}
|
||||
|
||||
function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
|
||||
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
|
||||
const nextDeps = deps === undefined ? null : deps;
|
||||
|
||||
if (workInProgressHook !== null) {
|
||||
const prevState = workInProgressHook.memoizedState;
|
||||
if (prevState !== null) {
|
||||
if (nextDeps !== null) {
|
||||
const prevDeps = prevState[1];
|
||||
if (areHookInputsEqual(nextDeps, prevDeps)) {
|
||||
return prevState[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = true;
|
||||
}
|
||||
const nextValue = nextCreate();
|
||||
if (__DEV__) {
|
||||
isInHookUserCodeInDev = false;
|
||||
}
|
||||
workInProgressHook.memoizedState = [nextValue, nextDeps];
|
||||
return nextValue;
|
||||
}
|
||||
|
||||
function useRef<T>(initialValue: T): {|current: T|} {
|
||||
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
|
||||
workInProgressHook = createWorkInProgressHook();
|
||||
const previousRef = workInProgressHook.memoizedState;
|
||||
if (previousRef === null) {
|
||||
const ref = {current: initialValue};
|
||||
if (__DEV__) {
|
||||
Object.seal(ref);
|
||||
}
|
||||
workInProgressHook.memoizedState = ref;
|
||||
return ref;
|
||||
} else {
|
||||
return previousRef;
|
||||
}
|
||||
}
|
||||
|
||||
export function useLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
inputs: Array<mixed> | void | null,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
currentHookNameInDev = 'useLayoutEffect';
|
||||
console.error(
|
||||
'useLayoutEffect does nothing on the server, because its effect cannot ' +
|
||||
"be encoded into the server renderer's output format. This will lead " +
|
||||
'to a mismatch between the initial, non-hydrated UI and the intended ' +
|
||||
'UI. To avoid this, useLayoutEffect should only be used in ' +
|
||||
'components that render exclusively on the client. ' +
|
||||
'See https://reactjs.org/link/uselayouteffect-ssr for common fixes.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchAction<A>(
|
||||
componentIdentity: Object,
|
||||
queue: UpdateQueue<A>,
|
||||
action: A,
|
||||
) {
|
||||
invariant(
|
||||
numberOfReRenders < RE_RENDER_LIMIT,
|
||||
'Too many re-renders. React limits the number of renders to prevent ' +
|
||||
'an infinite loop.',
|
||||
);
|
||||
|
||||
if (componentIdentity === currentlyRenderingComponent) {
|
||||
// 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<A> = {
|
||||
action,
|
||||
next: null,
|
||||
};
|
||||
if (renderPhaseUpdates === null) {
|
||||
renderPhaseUpdates = new Map();
|
||||
}
|
||||
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
|
||||
if (firstRenderPhaseUpdate === undefined) {
|
||||
renderPhaseUpdates.set(queue, update);
|
||||
} else {
|
||||
// Append the update to the end of the list.
|
||||
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
|
||||
while (lastRenderPhaseUpdate.next !== null) {
|
||||
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
|
||||
}
|
||||
lastRenderPhaseUpdate.next = update;
|
||||
}
|
||||
} else {
|
||||
// This means an update has happened after the function component has
|
||||
// returned. On the server this is a no-op. In React Fiber, the update
|
||||
// would be scheduled for a future render.
|
||||
}
|
||||
}
|
||||
|
||||
export function useCallback<T>(
|
||||
callback: T,
|
||||
deps: Array<mixed> | void | null,
|
||||
): T {
|
||||
return useMemo(() => callback, deps);
|
||||
}
|
||||
|
||||
// TODO Decide on how to implement this hook for server rendering.
|
||||
// If a mutation occurs during render, consider triggering a Suspense boundary
|
||||
// and falling back to client rendering.
|
||||
function useMutableSource<Source, Snapshot>(
|
||||
source: MutableSource<Source>,
|
||||
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
|
||||
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
|
||||
): Snapshot {
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return getSnapshot(source._source);
|
||||
}
|
||||
|
||||
function useDeferredValue<T>(value: T): T {
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return value;
|
||||
}
|
||||
|
||||
function unsupportedStartTransition() {
|
||||
invariant(false, 'startTransition cannot be called during server rendering.');
|
||||
}
|
||||
|
||||
function useTransition(): [(callback: () => void) => void, boolean] {
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return [unsupportedStartTransition, false];
|
||||
}
|
||||
|
||||
function useOpaqueIdentifier(): OpaqueIDType {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
function unsupportedRefresh() {
|
||||
invariant(false, 'Cache cannot be refreshed during server rendering.');
|
||||
}
|
||||
|
||||
function useCacheRefresh(): <T>(?() => T, ?T) => void {
|
||||
return unsupportedRefresh;
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export const Dispatcher: DispatcherType = {
|
||||
readContext,
|
||||
useContext,
|
||||
useMemo,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useLayoutEffect,
|
||||
useCallback,
|
||||
// useImperativeHandle is not run in the server environment
|
||||
useImperativeHandle: noop,
|
||||
// Effects are not run in the server environment.
|
||||
useEffect: noop,
|
||||
// Debugging effect
|
||||
useDebugValue: noop,
|
||||
useDeferredValue,
|
||||
useTransition,
|
||||
useOpaqueIdentifier,
|
||||
// Subscriptions are not setup in a server environment.
|
||||
useMutableSource,
|
||||
};
|
||||
|
||||
if (enableCache) {
|
||||
Dispatcher.getCacheForType = getCacheForType;
|
||||
Dispatcher.useCacheRefresh = useCacheRefresh;
|
||||
}
|
||||
+40
-56
@@ -7,7 +7,6 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {
|
||||
Destination,
|
||||
Chunk,
|
||||
@@ -70,6 +69,12 @@ import {
|
||||
pushProvider,
|
||||
popProvider,
|
||||
} from './ReactFizzNewContext';
|
||||
import {
|
||||
prepareToUseHooks,
|
||||
finishHooks,
|
||||
resetHooksState,
|
||||
Dispatcher,
|
||||
} from './ReactFizzHooks';
|
||||
|
||||
import {
|
||||
getIteratorFn,
|
||||
@@ -199,6 +204,8 @@ function defaultErrorHandler(error: mixed) {
|
||||
console['error'](error); // Don't transform to our wrapper
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export function createRequest(
|
||||
children: ReactNodeList,
|
||||
destination: Destination,
|
||||
@@ -474,8 +481,10 @@ function renderWithHooks<Props, SecondArg>(
|
||||
props: Props,
|
||||
secondArg: SecondArg,
|
||||
): any {
|
||||
// TODO: Set up Hooks etc.
|
||||
const children = Component(props, secondArg);
|
||||
const componentIdentity = {};
|
||||
prepareToUseHooks(componentIdentity);
|
||||
const result = Component(props, secondArg);
|
||||
const children = finishHooks(Component, props, result, secondArg);
|
||||
if (children === undefined) {
|
||||
invalidRenderResult(Component);
|
||||
}
|
||||
@@ -719,13 +728,29 @@ function validateFunctionComponentInDev(Component: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDefaultProps(Component: any, baseProps: Object): Object {
|
||||
if (Component && Component.defaultProps) {
|
||||
// Resolve default props. Taken from ReactElement
|
||||
const props = Object.assign({}, baseProps);
|
||||
const defaultProps = Component.defaultProps;
|
||||
for (const propName in defaultProps) {
|
||||
if (props[propName] === undefined) {
|
||||
props[propName] = defaultProps[propName];
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
return baseProps;
|
||||
}
|
||||
|
||||
function renderForwardRef(
|
||||
request: Request,
|
||||
task: Task,
|
||||
type: any,
|
||||
props: Object,
|
||||
ref: any,
|
||||
): void {
|
||||
throw new Error('Not yet implemented element type.');
|
||||
renderWithHooks(request, task, type, props, ref);
|
||||
}
|
||||
|
||||
function renderMemo(
|
||||
@@ -733,8 +758,11 @@ function renderMemo(
|
||||
task: Task,
|
||||
type: any,
|
||||
props: Object,
|
||||
ref: any,
|
||||
): void {
|
||||
throw new Error('Not yet implemented element type.');
|
||||
const innerType = type.type;
|
||||
const resolvedProps = resolveDefaultProps(innerType, props);
|
||||
renderElement(request, task, innerType, resolvedProps, ref);
|
||||
}
|
||||
|
||||
function renderContextConsumer(
|
||||
@@ -826,6 +854,7 @@ function renderElement(
|
||||
task: Task,
|
||||
type: any,
|
||||
props: Object,
|
||||
ref: any,
|
||||
): void {
|
||||
if (typeof type === 'function') {
|
||||
if (shouldConstruct(type)) {
|
||||
@@ -868,11 +897,11 @@ function renderElement(
|
||||
if (typeof type === 'object' && type !== null) {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_FORWARD_REF_TYPE: {
|
||||
renderForwardRef(request, task, type, props);
|
||||
renderForwardRef(request, task, type, props, ref);
|
||||
return;
|
||||
}
|
||||
case REACT_MEMO_TYPE: {
|
||||
renderMemo(request, task, type, props);
|
||||
renderMemo(request, task, type, props, ref);
|
||||
return;
|
||||
}
|
||||
case REACT_PROVIDER_TYPE: {
|
||||
@@ -952,7 +981,8 @@ function renderNodeDestructive(
|
||||
const element: React$Element<any> = (node: any);
|
||||
const type = element.type;
|
||||
const props = element.props;
|
||||
renderElement(request, task, type, props);
|
||||
const ref = element.ref;
|
||||
renderElement(request, task, type, props, ref);
|
||||
return;
|
||||
}
|
||||
case REACT_PORTAL_TYPE:
|
||||
@@ -1102,6 +1132,7 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
|
||||
try {
|
||||
return renderNodeDestructive(request, task, node);
|
||||
} catch (x) {
|
||||
resetHooksState();
|
||||
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
|
||||
spawnNewSuspendedTask(request, task, x);
|
||||
// Restore the context. We assume that this will be restored by the inner
|
||||
@@ -1290,6 +1321,7 @@ function retryTask(request: Request, task: Task): void {
|
||||
segment.status = COMPLETED;
|
||||
finishedTask(request, task.blockedBoundary, segment);
|
||||
} catch (x) {
|
||||
resetHooksState();
|
||||
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
|
||||
// Something suspended again, let's pick it back up later.
|
||||
const ping = task.ping;
|
||||
@@ -1704,51 +1736,3 @@ export function abort(request: Request): void {
|
||||
fatalError(request, error);
|
||||
}
|
||||
}
|
||||
|
||||
function notYetImplemented(): void {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
function unsupportedRefresh() {
|
||||
invariant(false, 'Cache cannot be refreshed during server rendering.');
|
||||
}
|
||||
|
||||
function unsupportedStartTransition() {
|
||||
invariant(false, 'startTransition cannot be called during server rendering.');
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
const Dispatcher: DispatcherType = {
|
||||
useMemo<T>(nextCreate: () => T): T {
|
||||
return nextCreate();
|
||||
},
|
||||
useCallback<T>(callback: T): T {
|
||||
return callback;
|
||||
},
|
||||
useDebugValue(): void {},
|
||||
useDeferredValue<T>(value: T): T {
|
||||
return value;
|
||||
},
|
||||
useTransition(): [(callback: () => void) => void, boolean] {
|
||||
return [unsupportedStartTransition, false];
|
||||
},
|
||||
getCacheForType<T>(resourceType: () => T): T {
|
||||
throw new Error('Not yet implemented. Should mark as client rendered.');
|
||||
},
|
||||
readContext: (notYetImplemented: any),
|
||||
useContext: (notYetImplemented: any),
|
||||
useReducer: (notYetImplemented: any),
|
||||
useRef: (notYetImplemented: any),
|
||||
useState: (notYetImplemented: any),
|
||||
useLayoutEffect: noop,
|
||||
// useImperativeHandle is not run in the server environment
|
||||
useImperativeHandle: noop,
|
||||
// Effects are not run in the server environment.
|
||||
useEffect: noop,
|
||||
useOpaqueIdentifier: (notYetImplemented: any),
|
||||
useMutableSource: (notYetImplemented: any),
|
||||
useCacheRefresh(): <T>(?() => T, ?T) => void {
|
||||
return unsupportedRefresh;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user