From bfb65681e7d77ba9bd79f7f95ac57930542b57c1 Mon Sep 17 00:00:00 2001 From: mofeiZ <34200447+mofeiZ@users.noreply.github.com> Date: Wed, 7 Sep 2022 13:09:42 -0400 Subject: [PATCH] experimental_use(context)(#25202) --- .../src/ReactFiberHooks.new.js | 113 +++++++++--------- .../src/ReactFiberHooks.old.js | 113 +++++++++--------- .../src/__tests__/ReactWakeable-test.js | 24 ++++ packages/shared/ReactTypes.js | 2 +- 4 files changed, 141 insertions(+), 111 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 2c06f94d58119..87a65254b40fd 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -36,6 +36,7 @@ import { enableUseHook, enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; +import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode'; import { @@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue { } function use(usable: Usable): T { - if ( - usable !== null && - typeof usable === 'object' && - typeof usable.then === 'function' - ) { - // This is a thenable. - const thenable: Thenable = (usable: any); - - // Track the position of the thenable within this fiber. - const index = thenableIndexCounter; - thenableIndexCounter += 1; - - switch (thenable.status) { - case 'fulfilled': { - const fulfilledValue: T = thenable.value; - return fulfilledValue; - } - case 'rejected': { - const rejectedError = thenable.reason; - throw rejectedError; - } - default: { - const prevThenableAtIndex: Thenable | null = getPreviouslyUsedThenableAtIndex( - index, - ); - if (prevThenableAtIndex !== null) { - switch (prevThenableAtIndex.status) { - case 'fulfilled': { - const fulfilledValue: T = prevThenableAtIndex.value; - return fulfilledValue; - } - case 'rejected': { - const rejectedError: mixed = prevThenableAtIndex.reason; - throw rejectedError; - } - default: { - // The thenable still hasn't resolved. Suspend with the same - // thenable as last time to avoid redundant listeners. - throw prevThenableAtIndex; + if (usable !== null && typeof usable === 'object') { + if (typeof usable.then === 'function') { + // This is a thenable. + const thenable: Thenable = (usable: any); + + // Track the position of the thenable within this fiber. + const index = thenableIndexCounter; + thenableIndexCounter += 1; + + switch (thenable.status) { + case 'fulfilled': { + const fulfilledValue: T = thenable.value; + return fulfilledValue; + } + case 'rejected': { + const rejectedError = thenable.reason; + throw rejectedError; + } + default: { + const prevThenableAtIndex: Thenable | null = getPreviouslyUsedThenableAtIndex( + index, + ); + if (prevThenableAtIndex !== null) { + switch (prevThenableAtIndex.status) { + case 'fulfilled': { + const fulfilledValue: T = prevThenableAtIndex.value; + return fulfilledValue; + } + case 'rejected': { + const rejectedError: mixed = prevThenableAtIndex.reason; + throw rejectedError; + } + default: { + // The thenable still hasn't resolved. Suspend with the same + // thenable as last time to avoid redundant listeners. + throw prevThenableAtIndex; + } } + } else { + // This is the first time something has been used at this index. + // Stash the thenable at the current index so we can reuse it during + // the next attempt. + trackUsedThenable(thenable, index); + + // Suspend. + // TODO: Throwing here is an implementation detail that allows us to + // unwind the call stack. But we shouldn't allow it to leak into + // userspace. Throw an opaque placeholder value instead of the + // actual thenable. If it doesn't get captured by the work loop, log + // a warning, because that means something in userspace must have + // caught it. + throw thenable; } - } else { - // This is the first time something has been used at this index. - // Stash the thenable at the current index so we can reuse it during - // the next attempt. - trackUsedThenable(thenable, index); - - // Suspend. - // TODO: Throwing here is an implementation detail that allows us to - // unwind the call stack. But we shouldn't allow it to leak into - // userspace. Throw an opaque placeholder value instead of the - // actual thenable. If it doesn't get captured by the work loop, log - // a warning, because that means something in userspace must have - // caught it. - throw thenable; } } + } else if ( + usable.$$typeof != null && + usable.$$typeof === REACT_CONTEXT_TYPE + ) { + const context: ReactContext = (usable: any); + return readContext(context); } } - // TODO: Add support for Context - // eslint-disable-next-line react-internal/safe-string-coercion throw new Error('An unsupported type was passed to use(): ' + String(usable)); } diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index e258a9f824e20..fd081fe5b0a68 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -36,6 +36,7 @@ import { enableUseHook, enableUseMemoCacheHook, } from 'shared/ReactFeatureFlags'; +import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode'; import { @@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue { } function use(usable: Usable): T { - if ( - usable !== null && - typeof usable === 'object' && - typeof usable.then === 'function' - ) { - // This is a thenable. - const thenable: Thenable = (usable: any); - - // Track the position of the thenable within this fiber. - const index = thenableIndexCounter; - thenableIndexCounter += 1; - - switch (thenable.status) { - case 'fulfilled': { - const fulfilledValue: T = thenable.value; - return fulfilledValue; - } - case 'rejected': { - const rejectedError = thenable.reason; - throw rejectedError; - } - default: { - const prevThenableAtIndex: Thenable | null = getPreviouslyUsedThenableAtIndex( - index, - ); - if (prevThenableAtIndex !== null) { - switch (prevThenableAtIndex.status) { - case 'fulfilled': { - const fulfilledValue: T = prevThenableAtIndex.value; - return fulfilledValue; - } - case 'rejected': { - const rejectedError: mixed = prevThenableAtIndex.reason; - throw rejectedError; - } - default: { - // The thenable still hasn't resolved. Suspend with the same - // thenable as last time to avoid redundant listeners. - throw prevThenableAtIndex; + if (usable !== null && typeof usable === 'object') { + if (typeof usable.then === 'function') { + // This is a thenable. + const thenable: Thenable = (usable: any); + + // Track the position of the thenable within this fiber. + const index = thenableIndexCounter; + thenableIndexCounter += 1; + + switch (thenable.status) { + case 'fulfilled': { + const fulfilledValue: T = thenable.value; + return fulfilledValue; + } + case 'rejected': { + const rejectedError = thenable.reason; + throw rejectedError; + } + default: { + const prevThenableAtIndex: Thenable | null = getPreviouslyUsedThenableAtIndex( + index, + ); + if (prevThenableAtIndex !== null) { + switch (prevThenableAtIndex.status) { + case 'fulfilled': { + const fulfilledValue: T = prevThenableAtIndex.value; + return fulfilledValue; + } + case 'rejected': { + const rejectedError: mixed = prevThenableAtIndex.reason; + throw rejectedError; + } + default: { + // The thenable still hasn't resolved. Suspend with the same + // thenable as last time to avoid redundant listeners. + throw prevThenableAtIndex; + } } + } else { + // This is the first time something has been used at this index. + // Stash the thenable at the current index so we can reuse it during + // the next attempt. + trackUsedThenable(thenable, index); + + // Suspend. + // TODO: Throwing here is an implementation detail that allows us to + // unwind the call stack. But we shouldn't allow it to leak into + // userspace. Throw an opaque placeholder value instead of the + // actual thenable. If it doesn't get captured by the work loop, log + // a warning, because that means something in userspace must have + // caught it. + throw thenable; } - } else { - // This is the first time something has been used at this index. - // Stash the thenable at the current index so we can reuse it during - // the next attempt. - trackUsedThenable(thenable, index); - - // Suspend. - // TODO: Throwing here is an implementation detail that allows us to - // unwind the call stack. But we shouldn't allow it to leak into - // userspace. Throw an opaque placeholder value instead of the - // actual thenable. If it doesn't get captured by the work loop, log - // a warning, because that means something in userspace must have - // caught it. - throw thenable; } } + } else if ( + usable.$$typeof != null && + usable.$$typeof === REACT_CONTEXT_TYPE + ) { + const context: ReactContext = (usable: any); + return readContext(context); } } - // TODO: Add support for Context - // eslint-disable-next-line react-internal/safe-string-coercion throw new Error('An unsupported type was passed to use(): ' + String(usable)); } diff --git a/packages/react-reconciler/src/__tests__/ReactWakeable-test.js b/packages/react-reconciler/src/__tests__/ReactWakeable-test.js index 7d946e5cbd03c..c0ec87f266413 100644 --- a/packages/react-reconciler/src/__tests__/ReactWakeable-test.js +++ b/packages/react-reconciler/src/__tests__/ReactWakeable-test.js @@ -315,4 +315,28 @@ describe('ReactWakeable', () => { ]); expect(root).toMatchRenderedOutput('Caught an error: Oops!'); }); + + // @gate enableUseHook + test('basic use(context)', () => { + const ContextA = React.createContext(''); + const ContextB = React.createContext('B'); + + function Sync() { + const text = use(ContextA) + use(ContextB); + return text; + } + + function App() { + return ( + + + + ); + } + + const root = ReactNoop.createRoot(); + root.render(); + expect(Scheduler).toFlushWithoutYielding(); + expect(root).toMatchRenderedOutput('AB'); + }); }); diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 7dacd489e4b8a..6e829a3a9f689 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -214,4 +214,4 @@ export type StartTransitionOptions = { }; // TODO: Add Context support -export type Usable = Thenable; +export type Usable = Thenable | ReactContext;