@samitouri / QOS-React-2 / commits / 0bd9b5d006

[Fresh] Support re-rendering lazy() without losing state (#15686)

* Support re-rendering lazy() without losing state * Clearer naming

Dan Abramov committed May 20, 2019 at 15:47 UTC 0bd9b5d006bec02b7f4149a618e5433f1cc6c6bd
2 files changed +75 -17
packages/react-dom/src/__tests__/ReactFresh-test.internal.js
+51 -5
@@ -897,7 +897,7 @@ describe('ReactFresh', () => {
897
898 it('can preserve state for lazy after resolution', async () => {
899 if (__DEV__) {
900 - let OuterV1 = render(() => {
900 + let AppV1 = render(() => {
901 function Hello() {
902 const [val, setVal] = React.useState(0);
903 return (
@@ -923,6 +923,7 @@ describe('ReactFresh', () => {
923 </React.Suspense>
924 );
925 }
926 + __register__(App, 'App');
927
928 return App;
929 });
@@ -943,7 +944,7 @@ describe('ReactFresh', () => {
944 expect(el.textContent).toBe('1');
945
946 // Perform a hot update.
946 - patch(() => {
947 + let AppV2 = patch(() => {
948 function Hello() {
949 const [val, setVal] = React.useState(0);
950 return (
@@ -953,6 +954,25 @@ describe('ReactFresh', () => {
954 );
955 }
956 __register__(Hello, 'Hello');
957 +
958 + const Outer = React.lazy(
959 + () =>
960 + new Promise(resolve => {
961 + setTimeout(() => resolve({default: Hello}), 100);
962 + }),
963 + );
964 + __register__(Outer, 'Outer');
965 +
966 + function App() {
967 + return (
968 + <React.Suspense fallback={<p>Loading</p>}>
969 + <Outer />
970 + </React.Suspense>
971 + );
972 + }
973 + __register__(App, 'App');
974 +
975 + return App;
976 });
977
978 // Assert the state was preserved but color changed.
@@ -968,8 +988,12 @@ describe('ReactFresh', () => {
988 expect(el.textContent).toBe('2');
989 expect(el.style.color).toBe('red');
990
971 - // Perform a top-down render with a stale type.
972 - render(() => OuterV1);
991 + // Perform top-down renders with both fresh and stale types.
992 + // Neither should change the state or color.
993 + // They should always resolve to the latest version.
994 + render(() => AppV1);
995 + render(() => AppV2);
996 + render(() => AppV1);
997 expect(container.firstChild).toBe(el);
998 expect(el.textContent).toBe('2');
999 expect(el.style.color).toBe('red');
@@ -987,7 +1011,17 @@ describe('ReactFresh', () => {
1011 __register__(Hello, 'Hello');
1012
1013 // Note: no lazy wrapper this time.
990 - return Hello;
1014 +
1015 + function App() {
1016 + return (
1017 + <React.Suspense fallback={<p>Loading</p>}>
1018 + <Hello />
1019 + </React.Suspense>
1020 + );
1021 + }
1022 + __register__(App, 'App');
1023 +
1024 + return App;
1025 });
1026
1027 expect(container.firstChild).not.toBe(el);
@@ -2053,6 +2087,18 @@ describe('ReactFresh', () => {
2087 }
2088 });
2089
2090 + it('can remount on signature change within a lazy simple memo wrapper', () => {
2091 + if (__DEV__) {
2092 + testRemountingWithWrapper(Hello =>
2093 + React.lazy(() => ({
2094 + then(cb) {
2095 + cb({default: React.memo(Hello)});
2096 + },
2097 + })),
2098 + );
2099 + }
2100 + });
2101 +
2102 it('can remount on signature change within forwardRef', () => {
2103 if (__DEV__) {
2104 testRemountingWithWrapper(Hello => React.forwardRef(Hello));
packages/react-reconciler/src/ReactFiberHotReloading.js
+24 -12
@@ -23,7 +23,11 @@ import {
23 MemoComponent,
24 SimpleMemoComponent,
25 } from 'shared/ReactWorkTags';
26 -import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols';
26 +import {
27 + REACT_FORWARD_REF_TYPE,
28 + REACT_MEMO_TYPE,
29 + REACT_LAZY_TYPE,
30 +} from 'shared/ReactSymbols';
31
32 type Family = {|
33 currentType: any,
@@ -108,39 +112,47 @@ export function isCompatibleFamilyForHotReloading(
112
113 const prevType = fiber.elementType;
114 const nextType = element.type;
115 +
116 // If we got here, we know types aren't === equal.
117 let needsCompareFamilies = false;
118 +
119 + const $$typeofNextType =
120 + typeof nextType === 'object' && nextType !== null
121 + ? nextType.$$typeof
122 + : null;
123 +
124 switch (fiber.tag) {
125 case FunctionComponent: {
126 if (typeof nextType === 'function') {
127 needsCompareFamilies = true;
128 + } else if ($$typeofNextType === REACT_LAZY_TYPE) {
129 + // We don't know the inner type yet.
130 + // We're going to assume that the lazy inner type is stable,
131 + // and so it is sufficient to avoid reconciling it away.
132 + // We're not going to unwrap or actually use the new lazy type.
133 + needsCompareFamilies = true;
134 }
135 break;
136 }
137 case ForwardRef: {
121 - if (
122 - typeof nextType === 'object' &&
123 - nextType !== null &&
124 - nextType.$$typeof === REACT_FORWARD_REF_TYPE
125 - ) {
138 + if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
139 + needsCompareFamilies = true;
140 + } else if ($$typeofNextType === REACT_LAZY_TYPE) {
141 needsCompareFamilies = true;
142 }
143 break;
144 }
145 case MemoComponent:
146 case SimpleMemoComponent: {
132 - if (
133 - typeof nextType === 'object' &&
134 - nextType !== null &&
135 - nextType.$$typeof === REACT_MEMO_TYPE
136 - ) {
147 + if ($$typeofNextType === REACT_MEMO_TYPE) {
148 // TODO: if it was but can no longer be simple,
149 // we shouldn't set this.
150 needsCompareFamilies = true;
151 + } else if ($$typeofNextType === REACT_LAZY_TYPE) {
152 + needsCompareFamilies = true;
153 }
154 break;
155 }
143 - // TODO: maybe support lazy?
156 default:
157 return false;
158 }