@samitouri / QOS-React-2 / commits / 46f912fd57

[react-core] Add more support for experimental React Scope API (#16621)

Dominic Gannaway committed Aug 30, 2019 at 18:27 UTC 46f912fd57604bd3c3949b5e8d6cc30a843ab1b9
7 files changed +229 -14
packages/react-dom/src/server/ReactPartialRenderer.js
+27
@@ -32,6 +32,7 @@ import {
32 enableSuspenseServerRenderer,
33 enableFundamentalAPI,
34 enableFlareAPI,
35 + enableScopeAPI,
36 } from 'shared/ReactFeatureFlags';
37
38 import {
@@ -48,6 +49,7 @@ import {
49 REACT_LAZY_TYPE,
50 REACT_MEMO_TYPE,
51 REACT_FUNDAMENTAL_TYPE,
52 + REACT_SCOPE_TYPE,
53 } from 'shared/ReactSymbols';
54
55 import {
@@ -1285,6 +1287,31 @@ class ReactDOMServerRenderer {
1287 );
1288 }
1289 }
1290 + // eslint-disable-next-line-no-fallthrough
1291 + case REACT_SCOPE_TYPE: {
1292 + if (enableScopeAPI) {
1293 + const nextChildren = toArray(
1294 + ((nextChild: any): ReactElement).props.children,
1295 + );
1296 + const frame: Frame = {
1297 + type: null,
1298 + domNamespace: parentNamespace,
1299 + children: nextChildren,
1300 + childIndex: 0,
1301 + context: context,
1302 + footer: '',
1303 + };
1304 + if (__DEV__) {
1305 + ((frame: any): FrameDev).debugElementStack = [];
1306 + }
1307 + this.stack.push(frame);
1308 + return '';
1309 + }
1310 + invariant(
1311 + false,
1312 + 'ReactDOMServer does not yet support scope components.',
1313 + );
1314 + }
1315 }
1316 }
1317
packages/react-reconciler/src/ReactFiberScope.js
+16 -11
@@ -14,11 +14,14 @@ import type {
14 ReactScopeMethods,
15 } from 'shared/ReactTypes';
16
17 +import {getPublicInstance} from './ReactFiberHostConfig';
18 +
19 import {
20 HostComponent,
21 SuspenseComponent,
22 ScopeComponent,
23 } from 'shared/ReactWorkTags';
24 +import {enableScopeAPI} from 'shared/ReactFeatureFlags';
25
26 function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
27 return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
@@ -33,19 +36,21 @@ function collectScopedNodes(
36 fn: (type: string | Object, props: Object) => boolean,
37 scopedNodes: Array<any>,
38 ): void {
36 - if (node.tag === HostComponent) {
37 - const {type, memoizedProps} = node;
38 - if (fn(type, memoizedProps) === true) {
39 - scopedNodes.push(node.stateNode);
39 + if (enableScopeAPI) {
40 + if (node.tag === HostComponent) {
41 + const {type, memoizedProps} = node;
42 + if (fn(type, memoizedProps) === true) {
43 + scopedNodes.push(getPublicInstance(node.stateNode));
44 + }
45 }
41 - }
42 - let child = node.child;
46 + let child = node.child;
47
44 - if (isFiberSuspenseAndTimedOut(node)) {
45 - child = getSuspenseFallbackChild(node);
46 - }
47 - if (child !== null) {
48 - collectScopedNodesFromChildren(child, fn, scopedNodes);
48 + if (isFiberSuspenseAndTimedOut(node)) {
49 + child = getSuspenseFallbackChild(node);
50 + }
51 + if (child !== null) {
52 + collectScopedNodesFromChildren(child, fn, scopedNodes);
53 + }
54 }
55 }
56
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+180
@@ -164,5 +164,185 @@ describe('ReactScope', () => {
164 const aChildren = refA.current.getChildren();
165 expect(aChildren).toEqual([refC.current]);
166 });
167 +
168 + it('scopes support server-side rendering and hydration', () => {
169 + const TestScope = React.unstable_createScope((type, props) => true);
170 + const ReactDOMServer = require('react-dom/server');
171 + const scopeRef = React.createRef();
172 + const divRef = React.createRef();
173 + const spanRef = React.createRef();
174 + const aRef = React.createRef();
175 +
176 + function Test({toggle}) {
177 + return (
178 + <div>
179 + <TestScope ref={scopeRef}>
180 + <div ref={divRef}>DIV</div>
181 + <span ref={spanRef}>SPAN</span>
182 + <a ref={aRef}>A</a>
183 + </TestScope>
184 + <div>Outside content!</div>
185 + </div>
186 + );
187 + }
188 + const html = ReactDOMServer.renderToString(<Test />);
189 + expect(html).toBe(
190 + '<div data-reactroot=""><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
191 + );
192 + container.innerHTML = html;
193 + ReactDOM.hydrate(<Test />, container);
194 + const nodes = scopeRef.current.getScopedNodes();
195 + expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
196 + });
197 + });
198 +
199 + describe('ReactTestRenderer', () => {
200 + let ReactTestRenderer;
201 +
202 + beforeEach(() => {
203 + ReactTestRenderer = require('react-test-renderer');
204 + });
205 +
206 + it('getScopedNodes() works as intended', () => {
207 + const TestScope = React.unstable_createScope((type, props) => true);
208 + const scopeRef = React.createRef();
209 + const divRef = React.createRef();
210 + const spanRef = React.createRef();
211 + const aRef = React.createRef();
212 +
213 + function Test({toggle}) {
214 + return toggle ? (
215 + <TestScope ref={scopeRef}>
216 + <div ref={divRef}>DIV</div>
217 + <span ref={spanRef}>SPAN</span>
218 + <a ref={aRef}>A</a>
219 + </TestScope>
220 + ) : (
221 + <TestScope ref={scopeRef}>
222 + <a ref={aRef}>A</a>
223 + <div ref={divRef}>DIV</div>
224 + <span ref={spanRef}>SPAN</span>
225 + </TestScope>
226 + );
227 + }
228 +
229 + const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
230 + createNodeMock: element => {
231 + return element;
232 + },
233 + });
234 + let nodes = scopeRef.current.getScopedNodes();
235 + expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
236 + renderer.update(<Test toggle={false} />);
237 + nodes = scopeRef.current.getScopedNodes();
238 + expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
239 + });
240 +
241 + it('mixed getParent() and getScopedNodes() works as intended', () => {
242 + const TestScope = React.unstable_createScope((type, props) => true);
243 + const TestScope2 = React.unstable_createScope((type, props) => true);
244 + const refA = React.createRef();
245 + const refB = React.createRef();
246 + const refC = React.createRef();
247 + const refD = React.createRef();
248 + const spanA = React.createRef();
249 + const spanB = React.createRef();
250 + const divA = React.createRef();
251 + const divB = React.createRef();
252 +
253 + function Test() {
254 + return (
255 + <div>
256 + <TestScope ref={refA}>
257 + <span ref={spanA}>
258 + <TestScope2 ref={refB}>
259 + <div ref={divA}>
260 + <TestScope ref={refC}>
261 + <span ref={spanB}>
262 + <TestScope2 ref={refD}>
263 + <div ref={divB}>>Hello world</div>
264 + </TestScope2>
265 + </span>
266 + </TestScope>
267 + </div>
268 + </TestScope2>
269 + </span>
270 + </TestScope>
271 + </div>
272 + );
273 + }
274 +
275 + ReactTestRenderer.create(<Test />, {
276 + createNodeMock: element => {
277 + return element;
278 + },
279 + });
280 + const dParent = refD.current.getParent();
281 + expect(dParent).not.toBe(null);
282 + expect(dParent.getScopedNodes()).toEqual([
283 + divA.current,
284 + spanB.current,
285 + divB.current,
286 + ]);
287 + const cParent = refC.current.getParent();
288 + expect(cParent).not.toBe(null);
289 + expect(cParent.getScopedNodes()).toEqual([
290 + spanA.current,
291 + divA.current,
292 + spanB.current,
293 + divB.current,
294 + ]);
295 + expect(refB.current.getParent()).toBe(null);
296 + expect(refA.current.getParent()).toBe(null);
297 + });
298 +
299 + it('getChildren() works as intended', () => {
300 + const TestScope = React.unstable_createScope((type, props) => true);
301 + const TestScope2 = React.unstable_createScope((type, props) => true);
302 + const refA = React.createRef();
303 + const refB = React.createRef();
304 + const refC = React.createRef();
305 + const refD = React.createRef();
306 + const spanA = React.createRef();
307 + const spanB = React.createRef();
308 + const divA = React.createRef();
309 + const divB = React.createRef();
310 +
311 + function Test() {
312 + return (
313 + <div>
314 + <TestScope ref={refA}>
315 + <span ref={spanA}>
316 + <TestScope2 ref={refB}>
317 + <div ref={divA}>
318 + <TestScope ref={refC}>
319 + <span ref={spanB}>
320 + <TestScope2 ref={refD}>
321 + <div ref={divB}>>Hello world</div>
322 + </TestScope2>
323 + </span>
324 + </TestScope>
325 + </div>
326 + </TestScope2>
327 + </span>
328 + </TestScope>
329 + </div>
330 + );
331 + }
332 +
333 + ReactTestRenderer.create(<Test />, {
334 + createNodeMock: element => {
335 + return element;
336 + },
337 + });
338 + const dChildren = refD.current.getChildren();
339 + expect(dChildren).toBe(null);
340 + const cChildren = refC.current.getChildren();
341 + expect(cChildren).toBe(null);
342 + const bChildren = refB.current.getChildren();
343 + expect(bChildren).toEqual([refD.current]);
344 + const aChildren = refA.current.getChildren();
345 + expect(aChildren).toEqual([refC.current]);
346 + });
347 });
348 });
packages/react-test-renderer/src/ReactTestRenderer.js
+2
@@ -37,6 +37,7 @@ import {
37 MemoComponent,
38 SimpleMemoComponent,
39 IncompleteClassComponent,
40 + ScopeComponent,
41 } from 'shared/ReactWorkTags';
42 import invariant from 'shared/invariant';
43 import ReactVersion from 'shared/ReactVersion';
@@ -203,6 +204,7 @@ function toTree(node: ?Fiber) {
204 case ForwardRef:
205 case MemoComponent:
206 case IncompleteClassComponent:
207 + case ScopeComponent:
208 return childrenToTree(node.child);
209 default:
210 invariant(
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1 -1
@@ -26,7 +26,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
26 export const disableJavaScriptURLs = false;
27 export const enableFlareAPI = true;
28 export const enableFundamentalAPI = false;
29 -export const enableScopeAPI = false;
29 +export const enableScopeAPI = true;
30 export const enableJSXTransformAPI = true;
31 export const warnAboutUnmockedScheduler = true;
32 export const flushSuspenseFallbacksInTests = true;
packages/shared/forks/ReactFeatureFlags.www.js
+1 -1
@@ -73,7 +73,7 @@ export const enableFlareAPI = true;
73
74 export const enableFundamentalAPI = false;
75
76 -export const enableScopeAPI = false;
76 +export const enableScopeAPI = true;
77
78 export const enableJSXTransformAPI = true;
79
scripts/error-codes/codes.json
+2 -1
@@ -340,5 +340,6 @@
340 "339": "An invalid value was used as an event listener. Expect one or many event listeners created via React.unstable_useResponder().",
341 "340": "Threw in newly mounted dehydrated component. This is likely a bug in React. Please file an issue.",
342 "341": "We just came from a parent so we must have had a parent. This is a bug in React.",
343 - "342": "A React component suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."
343 + "342": "A React component suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.",
344 + "343": "ReactDOMServer does not yet support scope components."
345 }