experimental_use(context)(#25202)
mofeiZ committed
Sep 7, 2022 at 13:09 UTC
bfb65681e7d77ba9bd79f7f95ac57930542b57c1
4 files changed
+141
-111
packages/react-reconciler/src/ReactFiberHooks.new.js
+58
-55
@@ -36,6 +36,7 @@ import {
36
enableUseHook,
37
enableUseMemoCacheHook,
38
} from 'shared/ReactFeatureFlags';
39
+import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
40
41
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
42
import {
@@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
715
}
716
717
function use<T>(usable: Usable<T>): T {
717
- if (
718
- usable !== null &&
719
- typeof usable === 'object' &&
720
- typeof usable.then === 'function'
721
- ) {
722
- // This is a thenable.
723
- const thenable: Thenable<T> = (usable: any);
724
-
725
- // Track the position of the thenable within this fiber.
726
- const index = thenableIndexCounter;
727
- thenableIndexCounter += 1;
728
-
729
- switch (thenable.status) {
730
- case 'fulfilled': {
731
- const fulfilledValue: T = thenable.value;
732
- return fulfilledValue;
733
- }
734
- case 'rejected': {
735
- const rejectedError = thenable.reason;
736
- throw rejectedError;
737
- }
738
- default: {
739
- const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
740
- index,
741
- );
742
- if (prevThenableAtIndex !== null) {
743
- switch (prevThenableAtIndex.status) {
744
- case 'fulfilled': {
745
- const fulfilledValue: T = prevThenableAtIndex.value;
746
- return fulfilledValue;
747
- }
748
- case 'rejected': {
749
- const rejectedError: mixed = prevThenableAtIndex.reason;
750
- throw rejectedError;
751
- }
752
- default: {
753
- // The thenable still hasn't resolved. Suspend with the same
754
- // thenable as last time to avoid redundant listeners.
755
- throw prevThenableAtIndex;
718
+ if (usable !== null && typeof usable === 'object') {
719
+ if (typeof usable.then === 'function') {
720
+ // This is a thenable.
721
+ const thenable: Thenable<T> = (usable: any);
722
+
723
+ // Track the position of the thenable within this fiber.
724
+ const index = thenableIndexCounter;
725
+ thenableIndexCounter += 1;
726
+
727
+ switch (thenable.status) {
728
+ case 'fulfilled': {
729
+ const fulfilledValue: T = thenable.value;
730
+ return fulfilledValue;
731
+ }
732
+ case 'rejected': {
733
+ const rejectedError = thenable.reason;
734
+ throw rejectedError;
735
+ }
736
+ default: {
737
+ const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
738
+ index,
739
+ );
740
+ if (prevThenableAtIndex !== null) {
741
+ switch (prevThenableAtIndex.status) {
742
+ case 'fulfilled': {
743
+ const fulfilledValue: T = prevThenableAtIndex.value;
744
+ return fulfilledValue;
745
+ }
746
+ case 'rejected': {
747
+ const rejectedError: mixed = prevThenableAtIndex.reason;
748
+ throw rejectedError;
749
+ }
750
+ default: {
751
+ // The thenable still hasn't resolved. Suspend with the same
752
+ // thenable as last time to avoid redundant listeners.
753
+ throw prevThenableAtIndex;
754
+ }
755
}
756
+ } else {
757
+ // This is the first time something has been used at this index.
758
+ // Stash the thenable at the current index so we can reuse it during
759
+ // the next attempt.
760
+ trackUsedThenable(thenable, index);
761
+
762
+ // Suspend.
763
+ // TODO: Throwing here is an implementation detail that allows us to
764
+ // unwind the call stack. But we shouldn't allow it to leak into
765
+ // userspace. Throw an opaque placeholder value instead of the
766
+ // actual thenable. If it doesn't get captured by the work loop, log
767
+ // a warning, because that means something in userspace must have
768
+ // caught it.
769
+ throw thenable;
770
}
758
- } else {
759
- // This is the first time something has been used at this index.
760
- // Stash the thenable at the current index so we can reuse it during
761
- // the next attempt.
762
- trackUsedThenable(thenable, index);
763
-
764
- // Suspend.
765
- // TODO: Throwing here is an implementation detail that allows us to
766
- // unwind the call stack. But we shouldn't allow it to leak into
767
- // userspace. Throw an opaque placeholder value instead of the
768
- // actual thenable. If it doesn't get captured by the work loop, log
769
- // a warning, because that means something in userspace must have
770
- // caught it.
771
- throw thenable;
771
}
772
}
773
+ } else if (
774
+ usable.$$typeof != null &&
775
+ usable.$$typeof === REACT_CONTEXT_TYPE
776
+ ) {
777
+ const context: ReactContext<T> = (usable: any);
778
+ return readContext(context);
779
}
780
}
781
777
- // TODO: Add support for Context
778
-
782
// eslint-disable-next-line react-internal/safe-string-coercion
783
throw new Error('An unsupported type was passed to use(): ' + String(usable));
784
}
packages/react-reconciler/src/ReactFiberHooks.old.js
+58
-55
@@ -36,6 +36,7 @@ import {
36
enableUseHook,
37
enableUseMemoCacheHook,
38
} from 'shared/ReactFeatureFlags';
39
+import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
40
41
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
42
import {
@@ -714,68 +715,70 @@ function createFunctionComponentUpdateQueue(): FunctionComponentUpdateQueue {
715
}
716
717
function use<T>(usable: Usable<T>): T {
717
- if (
718
- usable !== null &&
719
- typeof usable === 'object' &&
720
- typeof usable.then === 'function'
721
- ) {
722
- // This is a thenable.
723
- const thenable: Thenable<T> = (usable: any);
724
-
725
- // Track the position of the thenable within this fiber.
726
- const index = thenableIndexCounter;
727
- thenableIndexCounter += 1;
728
-
729
- switch (thenable.status) {
730
- case 'fulfilled': {
731
- const fulfilledValue: T = thenable.value;
732
- return fulfilledValue;
733
- }
734
- case 'rejected': {
735
- const rejectedError = thenable.reason;
736
- throw rejectedError;
737
- }
738
- default: {
739
- const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
740
- index,
741
- );
742
- if (prevThenableAtIndex !== null) {
743
- switch (prevThenableAtIndex.status) {
744
- case 'fulfilled': {
745
- const fulfilledValue: T = prevThenableAtIndex.value;
746
- return fulfilledValue;
747
- }
748
- case 'rejected': {
749
- const rejectedError: mixed = prevThenableAtIndex.reason;
750
- throw rejectedError;
751
- }
752
- default: {
753
- // The thenable still hasn't resolved. Suspend with the same
754
- // thenable as last time to avoid redundant listeners.
755
- throw prevThenableAtIndex;
718
+ if (usable !== null && typeof usable === 'object') {
719
+ if (typeof usable.then === 'function') {
720
+ // This is a thenable.
721
+ const thenable: Thenable<T> = (usable: any);
722
+
723
+ // Track the position of the thenable within this fiber.
724
+ const index = thenableIndexCounter;
725
+ thenableIndexCounter += 1;
726
+
727
+ switch (thenable.status) {
728
+ case 'fulfilled': {
729
+ const fulfilledValue: T = thenable.value;
730
+ return fulfilledValue;
731
+ }
732
+ case 'rejected': {
733
+ const rejectedError = thenable.reason;
734
+ throw rejectedError;
735
+ }
736
+ default: {
737
+ const prevThenableAtIndex: Thenable<T> | null = getPreviouslyUsedThenableAtIndex(
738
+ index,
739
+ );
740
+ if (prevThenableAtIndex !== null) {
741
+ switch (prevThenableAtIndex.status) {
742
+ case 'fulfilled': {
743
+ const fulfilledValue: T = prevThenableAtIndex.value;
744
+ return fulfilledValue;
745
+ }
746
+ case 'rejected': {
747
+ const rejectedError: mixed = prevThenableAtIndex.reason;
748
+ throw rejectedError;
749
+ }
750
+ default: {
751
+ // The thenable still hasn't resolved. Suspend with the same
752
+ // thenable as last time to avoid redundant listeners.
753
+ throw prevThenableAtIndex;
754
+ }
755
}
756
+ } else {
757
+ // This is the first time something has been used at this index.
758
+ // Stash the thenable at the current index so we can reuse it during
759
+ // the next attempt.
760
+ trackUsedThenable(thenable, index);
761
+
762
+ // Suspend.
763
+ // TODO: Throwing here is an implementation detail that allows us to
764
+ // unwind the call stack. But we shouldn't allow it to leak into
765
+ // userspace. Throw an opaque placeholder value instead of the
766
+ // actual thenable. If it doesn't get captured by the work loop, log
767
+ // a warning, because that means something in userspace must have
768
+ // caught it.
769
+ throw thenable;
770
}
758
- } else {
759
- // This is the first time something has been used at this index.
760
- // Stash the thenable at the current index so we can reuse it during
761
- // the next attempt.
762
- trackUsedThenable(thenable, index);
763
-
764
- // Suspend.
765
- // TODO: Throwing here is an implementation detail that allows us to
766
- // unwind the call stack. But we shouldn't allow it to leak into
767
- // userspace. Throw an opaque placeholder value instead of the
768
- // actual thenable. If it doesn't get captured by the work loop, log
769
- // a warning, because that means something in userspace must have
770
- // caught it.
771
- throw thenable;
771
}
772
}
773
+ } else if (
774
+ usable.$$typeof != null &&
775
+ usable.$$typeof === REACT_CONTEXT_TYPE
776
+ ) {
777
+ const context: ReactContext<T> = (usable: any);
778
+ return readContext(context);
779
}
780
}
781
777
- // TODO: Add support for Context
778
-
782
// eslint-disable-next-line react-internal/safe-string-coercion
783
throw new Error('An unsupported type was passed to use(): ' + String(usable));
784
}
packages/react-reconciler/src/__tests__/ReactWakeable-test.js
+24
@@ -315,4 +315,28 @@ describe('ReactWakeable', () => {
315
]);
316
expect(root).toMatchRenderedOutput('Caught an error: Oops!');
317
});
318
+
319
+ // @gate enableUseHook
320
+ test('basic use(context)', () => {
321
+ const ContextA = React.createContext('');
322
+ const ContextB = React.createContext('B');
323
+
324
+ function Sync() {
325
+ const text = use(ContextA) + use(ContextB);
326
+ return text;
327
+ }
328
+
329
+ function App() {
330
+ return (
331
+ <ContextA.Provider value="A">
332
+ <Sync />
333
+ </ContextA.Provider>
334
+ );
335
+ }
336
+
337
+ const root = ReactNoop.createRoot();
338
+ root.render(<App />);
339
+ expect(Scheduler).toFlushWithoutYielding();
340
+ expect(root).toMatchRenderedOutput('AB');
341
+ });
342
});
packages/shared/ReactTypes.js
+1
-1
@@ -214,4 +214,4 @@ export type StartTransitionOptions = {
214
};
215
216
// TODO: Add Context support
217
-export type Usable<T> = Thenable<T>;
217
+export type Usable<T> = Thenable<T> | ReactContext<T>;