mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[react-core] Add experimental React Scope component API (#16587)
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
PASSIVE_NOT_SUPPORTED,
|
||||
} from 'legacy-events/EventSystemFlags';
|
||||
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
|
||||
import {HostComponent, SuspenseComponent} from 'shared/ReactWorkTags';
|
||||
import {HostComponent} from 'shared/ReactWorkTags';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ReactDOMEventResponder,
|
||||
@@ -238,28 +238,6 @@ const eventResponderContext: ReactDOMResponderContext = {
|
||||
}
|
||||
}
|
||||
},
|
||||
getFocusableElementsInScope(deep: boolean): Array<HTMLElement> {
|
||||
validateResponderContext();
|
||||
const focusableElements = [];
|
||||
const eventResponderInstance = ((currentInstance: any): ReactDOMEventResponderInstance);
|
||||
const currentResponder = eventResponderInstance.responder;
|
||||
let focusScopeFiber = eventResponderInstance.fiber;
|
||||
if (deep) {
|
||||
let deepNode = focusScopeFiber.return;
|
||||
while (deepNode !== null) {
|
||||
if (doesFiberHaveResponder(deepNode, currentResponder)) {
|
||||
focusScopeFiber = deepNode;
|
||||
}
|
||||
deepNode = deepNode.return;
|
||||
}
|
||||
}
|
||||
const child = focusScopeFiber.child;
|
||||
|
||||
if (child !== null) {
|
||||
collectFocusableElements(child, focusableElements);
|
||||
}
|
||||
return focusableElements;
|
||||
},
|
||||
getActiveDocument,
|
||||
objectAssign: Object.assign,
|
||||
getTimeStamp(): number {
|
||||
@@ -335,33 +313,6 @@ function validateEventValue(eventValue: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
function collectFocusableElements(
|
||||
node: Fiber,
|
||||
focusableElements: Array<HTMLElement>,
|
||||
): void {
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
const fallbackChild = getSuspenseFallbackChild(node);
|
||||
if (fallbackChild !== null) {
|
||||
collectFocusableElements(fallbackChild, focusableElements);
|
||||
}
|
||||
} else {
|
||||
if (isFiberHostComponentFocusable(node)) {
|
||||
focusableElements.push(node.stateNode);
|
||||
} else {
|
||||
const child = node.child;
|
||||
|
||||
if (child !== null) {
|
||||
collectFocusableElements(child, focusableElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sibling = node.sibling;
|
||||
|
||||
if (sibling !== null) {
|
||||
collectFocusableElements(sibling, focusableElements);
|
||||
}
|
||||
}
|
||||
|
||||
function doesFiberHaveResponder(
|
||||
fiber: Fiber,
|
||||
responder: ReactDOMEventResponder,
|
||||
@@ -382,33 +333,6 @@ function getActiveDocument(): Document {
|
||||
return ((currentDocument: any): Document);
|
||||
}
|
||||
|
||||
function isFiberHostComponentFocusable(fiber: Fiber): boolean {
|
||||
if (fiber.tag !== HostComponent) {
|
||||
return false;
|
||||
}
|
||||
const {type, memoizedProps} = fiber;
|
||||
if (memoizedProps.tabIndex === -1 || memoizedProps.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (memoizedProps.tabIndex === 0 || memoizedProps.contentEditable === true) {
|
||||
return true;
|
||||
}
|
||||
if (type === 'a' || type === 'area') {
|
||||
return !!memoizedProps.href && memoizedProps.rel !== 'ignore';
|
||||
}
|
||||
if (type === 'input') {
|
||||
return memoizedProps.type !== 'hidden' && memoizedProps.type !== 'file';
|
||||
}
|
||||
return (
|
||||
type === 'button' ||
|
||||
type === 'textarea' ||
|
||||
type === 'object' ||
|
||||
type === 'select' ||
|
||||
type === 'iframe' ||
|
||||
type === 'embed'
|
||||
);
|
||||
}
|
||||
|
||||
function processTimers(
|
||||
timers: Map<number, ResponderTimer>,
|
||||
delay: number,
|
||||
@@ -626,14 +550,6 @@ function validateResponderContext(): void {
|
||||
);
|
||||
}
|
||||
|
||||
function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
}
|
||||
|
||||
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
|
||||
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
|
||||
}
|
||||
|
||||
export function dispatchEventForResponderEventSystem(
|
||||
topLevelType: string,
|
||||
targetFiber: null | Fiber,
|
||||
|
||||
@@ -92,11 +92,6 @@ const event = { type: 'press', target, pointerType, x, y };
|
||||
context.dispatchEvent('onPress', event, DiscreteEvent);
|
||||
```
|
||||
|
||||
### getFocusableElementsInScope(): Array<Element>
|
||||
|
||||
Returns every DOM element that can be focused within the scope of the Event
|
||||
Responder instance.
|
||||
|
||||
### isTargetWithinNode(target: Element, element: Element): boolean
|
||||
|
||||
Returns `true` if `target` is a child of `element`.
|
||||
|
||||
27
packages/react-reconciler/src/ReactFiber.js
vendored
27
packages/react-reconciler/src/ReactFiber.js
vendored
@@ -15,6 +15,7 @@ import type {
|
||||
ReactEventResponder,
|
||||
ReactEventResponderInstance,
|
||||
ReactFundamentalComponent,
|
||||
ReactScope,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {RootTag} from 'shared/ReactRootTags';
|
||||
import type {WorkTag} from 'shared/ReactWorkTags';
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
enableProfilerTimer,
|
||||
enableFundamentalAPI,
|
||||
enableUserTimingAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
|
||||
import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
|
||||
@@ -56,6 +58,7 @@ import {
|
||||
SimpleMemoComponent,
|
||||
LazyComponent,
|
||||
FundamentalComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
|
||||
@@ -86,6 +89,7 @@ import {
|
||||
REACT_MEMO_TYPE,
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_FUNDAMENTAL_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
let hasBadMapPolyfill;
|
||||
@@ -674,6 +678,16 @@ export function createFiberFromTypeAndProps(
|
||||
);
|
||||
}
|
||||
break;
|
||||
case REACT_SCOPE_TYPE:
|
||||
if (enableScopeAPI) {
|
||||
return createFiberFromScope(
|
||||
type,
|
||||
pendingProps,
|
||||
mode,
|
||||
expirationTime,
|
||||
key,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let info = '';
|
||||
@@ -766,6 +780,19 @@ export function createFiberFromFundamental(
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function createFiberFromScope(
|
||||
scope: ReactScope,
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
expirationTime: ExpirationTime,
|
||||
key: null | string,
|
||||
) {
|
||||
const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
|
||||
fiber.type = scope;
|
||||
fiber.expirationTime = expirationTime;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
function createFiberFromProfiler(
|
||||
pendingProps: any,
|
||||
mode: TypeOfMode,
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
LazyComponent,
|
||||
IncompleteClassComponent,
|
||||
FundamentalComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
NoEffect,
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableFundamentalAPI,
|
||||
warnAboutDefaultPropsOnFunctionComponents,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
import shallowEqual from 'shared/shallowEqual';
|
||||
@@ -2672,6 +2674,19 @@ function updateFundamentalComponent(
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
function updateScopeComponent(current, workInProgress, renderExpirationTime) {
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const nextChildren = nextProps.children;
|
||||
|
||||
reconcileChildren(
|
||||
current,
|
||||
workInProgress,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
return workInProgress.child;
|
||||
}
|
||||
|
||||
export function markWorkInProgressReceivedUpdate() {
|
||||
didReceiveUpdate = true;
|
||||
}
|
||||
@@ -3158,6 +3173,16 @@ function beginWork(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ScopeComponent: {
|
||||
if (enableScopeAPI) {
|
||||
return updateScopeComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
renderExpirationTime,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
enableFlareAPI,
|
||||
enableFundamentalAPI,
|
||||
enableSuspenseCallback,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
FunctionComponent,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
SimpleMemoComponent,
|
||||
SuspenseListComponent,
|
||||
FundamentalComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
invokeGuardedCallback,
|
||||
@@ -617,6 +619,7 @@ function commitLifeCycles(
|
||||
case SuspenseListComponent:
|
||||
case IncompleteClassComponent:
|
||||
case FundamentalComponent:
|
||||
case ScopeComponent:
|
||||
return;
|
||||
default: {
|
||||
invariant(
|
||||
@@ -691,6 +694,10 @@ function commitAttachRef(finishedWork: Fiber) {
|
||||
default:
|
||||
instanceToUse = instance;
|
||||
}
|
||||
// Moved outside to ensure DCE works with this flag
|
||||
if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
|
||||
instanceToUse = instance.methods;
|
||||
}
|
||||
if (typeof ref === 'function') {
|
||||
ref(instanceToUse);
|
||||
} else {
|
||||
@@ -1383,6 +1390,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ScopeComponent: {
|
||||
if (enableScopeAPI) {
|
||||
const scopeInstance = finishedWork.stateNode;
|
||||
scopeInstance.fiber = finishedWork;
|
||||
}
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
invariant(
|
||||
false,
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {ReactFundamentalComponentInstance} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ReactFundamentalComponentInstance,
|
||||
ReactScopeInstance,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {
|
||||
Instance,
|
||||
@@ -47,6 +50,7 @@ import {
|
||||
LazyComponent,
|
||||
IncompleteClassComponent,
|
||||
FundamentalComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {NoMode, BatchedMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
@@ -112,6 +116,7 @@ import {
|
||||
enableSuspenseServerRenderer,
|
||||
enableFlareAPI,
|
||||
enableFundamentalAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
markSpawnedWork,
|
||||
@@ -123,6 +128,7 @@ import {createFundamentalStateInstance} from './ReactFiberFundamental';
|
||||
import {Never} from './ReactFiberExpirationTime';
|
||||
import {resetChildFibers} from './ReactChildFiber';
|
||||
import {updateEventListeners} from './ReactFiberEvents';
|
||||
import {createScopeMethods} from './ReactFiberScope';
|
||||
|
||||
function markUpdate(workInProgress: Fiber) {
|
||||
// Tag the fiber with an update effect. This turns a Placement into
|
||||
@@ -1213,6 +1219,31 @@ function completeWork(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ScopeComponent: {
|
||||
if (enableScopeAPI) {
|
||||
if (current === null) {
|
||||
const type = workInProgress.type;
|
||||
const scopeInstance: ReactScopeInstance = {
|
||||
fiber: workInProgress,
|
||||
methods: null,
|
||||
};
|
||||
workInProgress.stateNode = scopeInstance;
|
||||
scopeInstance.methods = createScopeMethods(type, scopeInstance);
|
||||
if (workInProgress.ref !== null) {
|
||||
markRef(workInProgress);
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (current.ref !== workInProgress.ref) {
|
||||
markRef(workInProgress);
|
||||
}
|
||||
if (workInProgress.ref !== null) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
invariant(
|
||||
false,
|
||||
|
||||
130
packages/react-reconciler/src/ReactFiberScope.js
vendored
Normal file
130
packages/react-reconciler/src/ReactFiberScope.js
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {
|
||||
ReactScope,
|
||||
ReactScopeInstance,
|
||||
ReactScopeMethods,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
HostComponent,
|
||||
SuspenseComponent,
|
||||
ScopeComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
|
||||
function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
}
|
||||
|
||||
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
|
||||
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
|
||||
}
|
||||
|
||||
function collectScopedNodes(
|
||||
node: Fiber,
|
||||
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);
|
||||
}
|
||||
}
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
}
|
||||
}
|
||||
|
||||
function collectScopedNodesFromChildren(
|
||||
startingChild: Fiber,
|
||||
fn: (type: string | Object, props: Object) => boolean,
|
||||
scopedNodes: Array<any>,
|
||||
): void {
|
||||
let child = startingChild;
|
||||
while (child !== null) {
|
||||
collectScopedNodes(child, fn, scopedNodes);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function collectNearestScopeMethods(
|
||||
node: Fiber,
|
||||
scope: ReactScope,
|
||||
childrenScopes: Array<ReactScopeMethods>,
|
||||
): void {
|
||||
if (node.tag === ScopeComponent && node.type === scope) {
|
||||
childrenScopes.push(node.stateNode.methods);
|
||||
} else {
|
||||
let child = node.child;
|
||||
|
||||
if (isFiberSuspenseAndTimedOut(node)) {
|
||||
child = getSuspenseFallbackChild(node);
|
||||
}
|
||||
if (child !== null) {
|
||||
collectNearestChildScopeMethods(child, scope, childrenScopes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectNearestChildScopeMethods(
|
||||
startingChild: Fiber,
|
||||
scope: ReactScope,
|
||||
childrenScopes: Array<ReactScopeMethods>,
|
||||
): void {
|
||||
let child = startingChild;
|
||||
while (child !== null) {
|
||||
collectNearestScopeMethods(child, scope, childrenScopes);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
export function createScopeMethods(
|
||||
scope: ReactScope,
|
||||
instance: ReactScopeInstance,
|
||||
): ReactScopeMethods {
|
||||
const fn = scope.fn;
|
||||
return {
|
||||
getChildren(): null | Array<ReactScopeMethods> {
|
||||
const currentFiber = ((instance.fiber: any): Fiber);
|
||||
const child = currentFiber.child;
|
||||
const childrenScopes = [];
|
||||
if (child !== null) {
|
||||
collectNearestChildScopeMethods(child, scope, childrenScopes);
|
||||
}
|
||||
return childrenScopes.length === 0 ? null : childrenScopes;
|
||||
},
|
||||
getParent(): null | ReactScopeMethods {
|
||||
let node = ((instance.fiber: any): Fiber).return;
|
||||
while (node !== null) {
|
||||
if (node.tag === ScopeComponent && node.type === scope) {
|
||||
return node.stateNode.methods;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getScopedNodes(): null | Array<Object> {
|
||||
const currentFiber = ((instance.fiber: any): Fiber);
|
||||
const child = currentFiber.child;
|
||||
const scopedNodes = [];
|
||||
if (child !== null) {
|
||||
collectScopedNodesFromChildren(child, fn, scopedNodes);
|
||||
}
|
||||
return scopedNodes.length === 0 ? null : scopedNodes;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
|
||||
describe('ReactScope', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableScopeAPI = true;
|
||||
React = require('react');
|
||||
});
|
||||
|
||||
describe('ReactDOM', () => {
|
||||
let ReactDOM;
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
ReactDOM = require('react-dom');
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test toggle={true} />, container);
|
||||
let nodes = scopeRef.current.getScopedNodes();
|
||||
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
|
||||
ReactDOM.render(<Test toggle={false} />, container);
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<Test />, container);
|
||||
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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -53,10 +53,12 @@ import {
|
||||
import ReactSharedInternals from './ReactSharedInternals';
|
||||
import createFundamental from 'shared/createFundamentalComponent';
|
||||
import createResponder from 'shared/createEventResponder';
|
||||
import createScope from 'shared/createScope';
|
||||
import {
|
||||
enableJSXTransformAPI,
|
||||
enableFlareAPI,
|
||||
enableFundamentalAPI,
|
||||
enableScopeAPI,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
const React = {
|
||||
Children: {
|
||||
@@ -114,6 +116,10 @@ if (enableFundamentalAPI) {
|
||||
React.unstable_createFundamental = createFundamental;
|
||||
}
|
||||
|
||||
if (enableScopeAPI) {
|
||||
React.unstable_createScope = createScope;
|
||||
}
|
||||
|
||||
// Note: some APIs are added with feature flags.
|
||||
// Make sure that stable builds for open source
|
||||
// don't modify the React object to avoid deopts.
|
||||
|
||||
@@ -66,7 +66,6 @@ export type ReactDOMResponderContext = {
|
||||
removeRootEventTypes: (rootEventTypes: Array<string>) => void,
|
||||
setTimeout: (func: () => void, timeout: number) => number,
|
||||
clearTimeout: (timerId: number) => void,
|
||||
getFocusableElementsInScope(deep: boolean): Array<HTMLElement>,
|
||||
getActiveDocument(): Document,
|
||||
objectAssign: Function,
|
||||
getTimeStamp: () => number,
|
||||
|
||||
@@ -65,6 +65,9 @@ export const enableFlareAPI = false;
|
||||
// Experimental Host Component support.
|
||||
export const enableFundamentalAPI = false;
|
||||
|
||||
// Experimental Scope support.
|
||||
export const enableScopeAPI = false;
|
||||
|
||||
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
|
||||
export const enableJSXTransformAPI = false;
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export const REACT_FUNDAMENTAL_TYPE = hasSymbol
|
||||
export const REACT_RESPONDER_TYPE = hasSymbol
|
||||
? Symbol.for('react.responder')
|
||||
: 0xead6;
|
||||
export const REACT_SCOPE_TYPE = hasSymbol ? Symbol.for('react.scope') : 0xead7;
|
||||
|
||||
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
|
||||
const FAUX_ITERATOR_SYMBOL = '@@iterator';
|
||||
|
||||
@@ -159,3 +159,19 @@ export type ReactFundamentalComponent<C, H> = {|
|
||||
$$typeof: Symbol | number,
|
||||
impl: ReactFundamentalImpl<C, H>,
|
||||
|};
|
||||
|
||||
export type ReactScope = {|
|
||||
$$typeof: Symbol | number,
|
||||
fn: (type: string | Object, props: Object) => boolean,
|
||||
|};
|
||||
|
||||
export type ReactScopeMethods = {|
|
||||
getChildren(): null | Array<ReactScopeMethods>,
|
||||
getParent(): null | ReactScopeMethods,
|
||||
getScopedNodes(): null | Array<Object>,
|
||||
|};
|
||||
|
||||
export type ReactScopeInstance = {|
|
||||
fiber: Object,
|
||||
methods: null | ReactScopeMethods,
|
||||
|};
|
||||
|
||||
@@ -28,7 +28,8 @@ export type WorkTag =
|
||||
| 17
|
||||
| 18
|
||||
| 19
|
||||
| 20;
|
||||
| 20
|
||||
| 21;
|
||||
|
||||
export const FunctionComponent = 0;
|
||||
export const ClassComponent = 1;
|
||||
@@ -51,3 +52,4 @@ export const IncompleteClassComponent = 17;
|
||||
export const DehydratedFragment = 18;
|
||||
export const SuspenseListComponent = 19;
|
||||
export const FundamentalComponent = 20;
|
||||
export const ScopeComponent = 21;
|
||||
|
||||
23
packages/shared/createScope.js
Normal file
23
packages/shared/createScope.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactScope} from 'shared/ReactTypes';
|
||||
import {REACT_SCOPE_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
export default function createScope(
|
||||
fn: (type: string | Object, props: Object) => boolean,
|
||||
): ReactScope {
|
||||
const scopeComponent = {
|
||||
$$typeof: REACT_SCOPE_TYPE,
|
||||
fn,
|
||||
};
|
||||
if (__DEV__) {
|
||||
Object.freeze(scopeComponent);
|
||||
}
|
||||
return scopeComponent;
|
||||
}
|
||||
@@ -33,6 +33,7 @@ export const warnAboutDeprecatedLifecycles = true;
|
||||
export const warnAboutDeprecatedSetNativeProps = true;
|
||||
export const enableFlareAPI = false;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutUnmockedScheduler = true;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
|
||||
export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const enableFlareAPI = false;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutUnmockedScheduler = false;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
|
||||
export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const enableFlareAPI = false;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutUnmockedScheduler = true;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
|
||||
export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const enableFlareAPI = false;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableJSXTransformAPI = false;
|
||||
export const warnAboutUnmockedScheduler = false;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -26,6 +26,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
|
||||
export const disableJavaScriptURLs = false;
|
||||
export const enableFlareAPI = true;
|
||||
export const enableFundamentalAPI = false;
|
||||
export const enableScopeAPI = false;
|
||||
export const enableJSXTransformAPI = true;
|
||||
export const warnAboutUnmockedScheduler = true;
|
||||
export const flushSuspenseFallbacksInTests = true;
|
||||
|
||||
@@ -73,6 +73,8 @@ export const enableFlareAPI = true;
|
||||
|
||||
export const enableFundamentalAPI = false;
|
||||
|
||||
export const enableScopeAPI = false;
|
||||
|
||||
export const enableJSXTransformAPI = true;
|
||||
|
||||
export const warnAboutUnmockedScheduler = true;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
REACT_LAZY_TYPE,
|
||||
REACT_FUNDAMENTAL_TYPE,
|
||||
REACT_RESPONDER_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
export default function isValidElementType(type: mixed) {
|
||||
@@ -42,6 +43,7 @@ export default function isValidElementType(type: mixed) {
|
||||
type.$$typeof === REACT_CONTEXT_TYPE ||
|
||||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
|
||||
type.$$typeof === REACT_FUNDAMENTAL_TYPE ||
|
||||
type.$$typeof === REACT_RESPONDER_TYPE))
|
||||
type.$$typeof === REACT_RESPONDER_TYPE ||
|
||||
type.$$typeof === REACT_SCOPE_TYPE))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user