mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[react-core] Add more support for experimental React Scope API (#16621)
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableFundamentalAPI,
|
||||
enableFlareAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
@@ -48,6 +49,7 @@ import {
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_FUNDAMENTAL_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import {
|
||||
@@ -1285,6 +1287,31 @@ class ReactDOMServerRenderer {
|
||||
);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
case REACT_SCOPE_TYPE: {
|
||||
if (enableScopeAPI) {
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
);
|
||||
const frame: Frame = {
|
||||
type: null,
|
||||
domNamespace: parentNamespace,
|
||||
children: nextChildren,
|
||||
childIndex: 0,
|
||||
context: context,
|
||||
footer: '',
|
||||
};
|
||||
if (__DEV__) {
|
||||
((frame: any): FrameDev).debugElementStack = [];
|
||||
}
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'ReactDOMServer does not yet support scope components.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-11
@@ -14,11 +14,14 @@ import type {
|
||||
ReactScopeMethods,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import {getPublicInstance} from './ReactFiberHostConfig';
|
||||
|
||||
import {
|
||||
HostComponent,
|
||||
SuspenseComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
@@ -33,19 +36,21 @@ function collectScopedNodes(
|
||||
fn: (type: string | Object, props: Object) => boolean,
|
||||
scopedNodes: Array<any>,
|
||||
): void {
|
||||
if (node.tag === HostComponent) {
|
||||
const {type, memoizedProps} = node;
|
||||
if (fn(type, memoizedProps) === true) {
|
||||
scopedNodes.push(node.stateNode);
|
||||
if (enableScopeAPI) {
|
||||
if (node.tag === HostComponent) {
|
||||
const {type, memoizedProps} = node;
|
||||
if (fn(type, memoizedProps) === true) {
|
||||
scopedNodes.push(getPublicInstance(node.stateNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
let child = node.child;
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,5 +164,185 @@ describe('ReactScope', () => {
|
||||
const aChildren = refA.current.getChildren();
|
||||
expect(aChildren).toEqual([refC.current]);
|
||||
});
|
||||
|
||||
it('scopes support server-side rendering and hydration', () => {
|
||||
const TestScope = React.unstable_createScope((type, props) => true);
|
||||
const ReactDOMServer = require('react-dom/server');
|
||||
const scopeRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
const spanRef = React.createRef();
|
||||
const aRef = React.createRef();
|
||||
|
||||
function Test({toggle}) {
|
||||
return (
|
||||
<div>
|
||||
<TestScope ref={scopeRef}>
|
||||
<div ref={divRef}>DIV</div>
|
||||
<span ref={spanRef}>SPAN</span>
|
||||
<a ref={aRef}>A</a>
|
||||
</TestScope>
|
||||
<div>Outside content!</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const html = ReactDOMServer.renderToString(<Test />);
|
||||
expect(html).toBe(
|
||||
'<div data-reactroot=""><div>DIV</div><span>SPAN</span><a>A</a><div>Outside content!</div></div>',
|
||||
);
|
||||
container.innerHTML = html;
|
||||
ReactDOM.hydrate(<Test />, container);
|
||||
const nodes = scopeRef.current.getScopedNodes();
|
||||
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactTestRenderer', () => {
|
||||
let ReactTestRenderer;
|
||||
|
||||
beforeEach(() => {
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
});
|
||||
|
||||
it('getScopedNodes() works as intended', () => {
|
||||
const TestScope = React.unstable_createScope((type, props) => true);
|
||||
const scopeRef = React.createRef();
|
||||
const divRef = React.createRef();
|
||||
const spanRef = React.createRef();
|
||||
const aRef = React.createRef();
|
||||
|
||||
function Test({toggle}) {
|
||||
return toggle ? (
|
||||
<TestScope ref={scopeRef}>
|
||||
<div ref={divRef}>DIV</div>
|
||||
<span ref={spanRef}>SPAN</span>
|
||||
<a ref={aRef}>A</a>
|
||||
</TestScope>
|
||||
) : (
|
||||
<TestScope ref={scopeRef}>
|
||||
<a ref={aRef}>A</a>
|
||||
<div ref={divRef}>DIV</div>
|
||||
<span ref={spanRef}>SPAN</span>
|
||||
</TestScope>
|
||||
);
|
||||
}
|
||||
|
||||
const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
|
||||
createNodeMock: element => {
|
||||
return element;
|
||||
},
|
||||
});
|
||||
let nodes = scopeRef.current.getScopedNodes();
|
||||
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
|
||||
renderer.update(<Test toggle={false} />);
|
||||
nodes = scopeRef.current.getScopedNodes();
|
||||
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
|
||||
});
|
||||
|
||||
it('mixed getParent() and getScopedNodes() works as intended', () => {
|
||||
const TestScope = React.unstable_createScope((type, props) => true);
|
||||
const TestScope2 = React.unstable_createScope((type, props) => true);
|
||||
const refA = React.createRef();
|
||||
const refB = React.createRef();
|
||||
const refC = React.createRef();
|
||||
const refD = React.createRef();
|
||||
const spanA = React.createRef();
|
||||
const spanB = React.createRef();
|
||||
const divA = React.createRef();
|
||||
const divB = React.createRef();
|
||||
|
||||
function Test() {
|
||||
return (
|
||||
<div>
|
||||
<TestScope ref={refA}>
|
||||
<span ref={spanA}>
|
||||
<TestScope2 ref={refB}>
|
||||
<div ref={divA}>
|
||||
<TestScope ref={refC}>
|
||||
<span ref={spanB}>
|
||||
<TestScope2 ref={refD}>
|
||||
<div ref={divB}>>Hello world</div>
|
||||
</TestScope2>
|
||||
</span>
|
||||
</TestScope>
|
||||
</div>
|
||||
</TestScope2>
|
||||
</span>
|
||||
</TestScope>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(<Test />, {
|
||||
createNodeMock: element => {
|
||||
return element;
|
||||
},
|
||||
});
|
||||
const dParent = refD.current.getParent();
|
||||
expect(dParent).not.toBe(null);
|
||||
expect(dParent.getScopedNodes()).toEqual([
|
||||
divA.current,
|
||||
spanB.current,
|
||||
divB.current,
|
||||
]);
|
||||
const cParent = refC.current.getParent();
|
||||
expect(cParent).not.toBe(null);
|
||||
expect(cParent.getScopedNodes()).toEqual([
|
||||
spanA.current,
|
||||
divA.current,
|
||||
spanB.current,
|
||||
divB.current,
|
||||
]);
|
||||
expect(refB.current.getParent()).toBe(null);
|
||||
expect(refA.current.getParent()).toBe(null);
|
||||
});
|
||||
|
||||
it('getChildren() works as intended', () => {
|
||||
const TestScope = React.unstable_createScope((type, props) => true);
|
||||
const TestScope2 = React.unstable_createScope((type, props) => true);
|
||||
const refA = React.createRef();
|
||||
const refB = React.createRef();
|
||||
const refC = React.createRef();
|
||||
const refD = React.createRef();
|
||||
const spanA = React.createRef();
|
||||
const spanB = React.createRef();
|
||||
const divA = React.createRef();
|
||||
const divB = React.createRef();
|
||||
|
||||
function Test() {
|
||||
return (
|
||||
<div>
|
||||
<TestScope ref={refA}>
|
||||
<span ref={spanA}>
|
||||
<TestScope2 ref={refB}>
|
||||
<div ref={divA}>
|
||||
<TestScope ref={refC}>
|
||||
<span ref={spanB}>
|
||||
<TestScope2 ref={refD}>
|
||||
<div ref={divB}>>Hello world</div>
|
||||
</TestScope2>
|
||||
</span>
|
||||
</TestScope>
|
||||
</div>
|
||||
</TestScope2>
|
||||
</span>
|
||||
</TestScope>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(<Test />, {
|
||||
createNodeMock: element => {
|
||||
return element;
|
||||
},
|
||||
});
|
||||
const dChildren = refD.current.getChildren();
|
||||
expect(dChildren).toBe(null);
|
||||
const cChildren = refC.current.getChildren();
|
||||
expect(cChildren).toBe(null);
|
||||
const bChildren = refB.current.getChildren();
|
||||
expect(bChildren).toEqual([refD.current]);
|
||||
const aChildren = refA.current.getChildren();
|
||||
expect(aChildren).toEqual([refC.current]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
MemoComponent,
|
||||
SimpleMemoComponent,
|
||||
IncompleteClassComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import invariant from 'shared/invariant';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
@@ -203,6 +204,7 @@ function toTree(node: ?Fiber) {
|
||||
case ForwardRef:
|
||||
case MemoComponent:
|
||||
case IncompleteClassComponent:
|
||||
case ScopeComponent:
|
||||
return childrenToTree(node.child);
|
||||
default:
|
||||
invariant(
|
||||
|
||||
@@ -26,7 +26,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const enableFlareAPI = true;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableScopeAPI = true;
|
||||
export const enableJSXTransformAPI = true;
|
||||
export const warnAboutUnmockedScheduler = true;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -73,7 +73,7 @@ export const enableFlareAPI = true;
|
||||
|
||||
export const enableFundamentalAPI = false;
|
||||
|
||||
export const enableScopeAPI = false;
|
||||
export const enableScopeAPI = true;
|
||||
|
||||
export const enableJSXTransformAPI = true;
|
||||
|
||||
|
||||
@@ -340,5 +340,6 @@
|
||||
"339": "An invalid value was used as an event listener. Expect one or many event listeners created via React.unstable_useResponder().",
|
||||
"340": "Threw in newly mounted dehydrated component. This is likely a bug in React. Please file an issue.",
|
||||
"341": "We just came from a parent so we must have had a parent. This is a bug in React.",
|
||||
"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."
|
||||
"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": "ReactDOMServer does not yet support scope components."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user