diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js index ecb99d9ab44..0827816b2b6 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js @@ -6,6 +6,7 @@ let SimpleCacheProvider; let Placeholder; let StrictMode; let AsyncMode; +let lazy; let cache; let TextResource; @@ -25,6 +26,7 @@ describe('ReactSuspense', () => { Placeholder = React.Placeholder; StrictMode = React.StrictMode; AsyncMode = React.unstable_AsyncMode; + lazy = React.lazy; function invalidateCache() { cache = SimpleCacheProvider.createCache(invalidateCache); @@ -1543,6 +1545,37 @@ describe('ReactSuspense', () => { expect(ReactNoop.flush()).toEqual(['B']); expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('B')]); }); + + it('lazy-load using React.lazy', async () => { + const LazyText = lazy(() => { + ReactNoop.yield('Started loading'); + return Promise.resolve(Text); + }); + + ReactNoop.render( + }> + + + + , + ); + // Render first two siblings. The lazy component should not have + // started loading yet. + ReactNoop.flushThrough(['A', 'B']); + + // Flush the rest. + expect(ReactNoop.flush()).toEqual(['Started loading', 'Loading...']); + expect(ReactNoop.getChildren()).toEqual([]); + + await LazyText; + + expect(ReactNoop.flush()).toEqual(['A', 'B', 'C']); + expect(ReactNoop.getChildren()).toEqual([ + span('A'), + span('B'), + span('C'), + ]); + }); }); it('does not call lifecycles of a suspended component', async () => { diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 15e10114dcd..85e1207a902 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -25,6 +25,7 @@ import { isValidElement, } from './ReactElement'; import {createContext} from './ReactContext'; +import {lazy} from './ReactLazy'; import forwardRef from './forwardRef'; import { createElementWithValidation, @@ -66,6 +67,7 @@ const React = { if (enableSuspense) { React.Placeholder = REACT_PLACEHOLDER_TYPE; + React.lazy = lazy; } export default React; diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js new file mode 100644 index 00000000000..e17c33b9793 --- /dev/null +++ b/packages/react/src/ReactLazy.js @@ -0,0 +1,27 @@ +/** + * 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. + */ + +type Thenable = { + then(resolve: (T) => mixed, reject: (mixed) => mixed): R, +}; + +export function lazy(ctor: () => Thenable) { + let thenable = null; + return { + then(resolve, reject) { + if (thenable === null) { + // Lazily create thenable by wrapping in an extra thenable. + thenable = ctor(); + ctor = null; + } + return thenable.then(resolve, reject); + }, + // React uses these fields to store the result. + _reactStatus: -1, + _reactResult: null, + }; +}