@samitouri / QOS-React-2 / commits / bfb65681e7

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