Adds experimental fundamental interface (#16049)
Dominic Gannaway committed
Jul 19, 2019 at 22:20 UTC
2c4d61e1022ae383dd11fe237f6df8451e6f0310
35 files changed
+898
-40
package.json
+1
@@ -104,6 +104,7 @@
104
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
105
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
106
"test-persistent": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source-persistent.js",
107
+ "debug-test-persistent": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source-persistent.js --runInBand",
108
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
109
"test-prod-build": "yarn test-build-prod",
110
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
packages/react-art/src/ReactARTHostConfig.js
+20
@@ -444,3 +444,23 @@ export function unmountEventComponent(
444
): void {
445
throw new Error('Not yet implemented.');
446
}
447
+
448
+export function getFundamentalComponentInstance(fundamentalInstance) {
449
+ throw new Error('Not yet implemented.');
450
+}
451
+
452
+export function mountFundamentalComponent(fundamentalInstance) {
453
+ throw new Error('Not yet implemented.');
454
+}
455
+
456
+export function shouldUpdateFundamentalComponent(fundamentalInstance) {
457
+ throw new Error('Not yet implemented.');
458
+}
459
+
460
+export function updateFundamentalComponent(fundamentalInstance) {
461
+ throw new Error('Not yet implemented.');
462
+}
463
+
464
+export function unmountFundamentalComponent(fundamentalInstance) {
465
+ throw new Error('Not yet implemented.');
466
+}
packages/react-dom/src/client/ReactDOMHostConfig.js
+64
@@ -45,6 +45,7 @@ import type {DOMContainer} from './ReactDOM';
45
import type {
46
ReactDOMEventResponder,
47
ReactDOMEventComponentInstance,
48
+ ReactDOMFundamentalComponentInstance,
49
} from 'shared/ReactDOMTypes';
50
import {
51
addRootEventTypesForComponentInstance,
@@ -103,6 +104,7 @@ export type NoTimeout = -1;
104
import {
105
enableSuspenseServerRenderer,
106
enableFlareAPI,
107
+ enableFundamentalAPI,
108
} from 'shared/ReactFeatureFlags';
109
import warning from 'shared/warning';
110
@@ -882,3 +884,65 @@ export function unmountEventComponent(
884
unmountEventResponder(eventComponentInstance);
885
}
886
}
887
+
888
+export function getFundamentalComponentInstance(
889
+ fundamentalInstance: ReactDOMFundamentalComponentInstance,
890
+): Instance {
891
+ if (enableFundamentalAPI) {
892
+ const {currentFiber, impl, props, state} = fundamentalInstance;
893
+ const instance = impl.getInstance(null, props, state);
894
+ precacheFiberNode(currentFiber, instance);
895
+ return instance;
896
+ }
897
+ // Because of the flag above, this gets around the Flow error;
898
+ return (null: any);
899
+}
900
+
901
+export function mountFundamentalComponent(
902
+ fundamentalInstance: ReactDOMFundamentalComponentInstance,
903
+): void {
904
+ if (enableFundamentalAPI) {
905
+ const {impl, instance, props, state} = fundamentalInstance;
906
+ const onMount = impl.onMount;
907
+ if (onMount !== undefined) {
908
+ onMount(null, instance, props, state);
909
+ }
910
+ }
911
+}
912
+
913
+export function shouldUpdateFundamentalComponent(
914
+ fundamentalInstance: ReactDOMFundamentalComponentInstance,
915
+): boolean {
916
+ if (enableFundamentalAPI) {
917
+ const {impl, prevProps, props, state} = fundamentalInstance;
918
+ const shouldUpdate = impl.shouldUpdate;
919
+ if (shouldUpdate !== undefined) {
920
+ return shouldUpdate(null, prevProps, props, state);
921
+ }
922
+ }
923
+ return true;
924
+}
925
+
926
+export function updateFundamentalComponent(
927
+ fundamentalInstance: ReactDOMFundamentalComponentInstance,
928
+): void {
929
+ if (enableFundamentalAPI) {
930
+ const {impl, instance, prevProps, props, state} = fundamentalInstance;
931
+ const onUpdate = impl.onUpdate;
932
+ if (onUpdate !== undefined) {
933
+ onUpdate(null, instance, prevProps, props, state);
934
+ }
935
+ }
936
+}
937
+
938
+export function unmountFundamentalComponent(
939
+ fundamentalInstance: ReactDOMFundamentalComponentInstance,
940
+): void {
941
+ if (enableFundamentalAPI) {
942
+ const {impl, instance, props, state} = fundamentalInstance;
943
+ const onUnmount = impl.onUnmount;
944
+ if (onUnmount !== undefined) {
945
+ onUnmount(null, instance, props, state);
946
+ }
947
+ }
948
+}
packages/react-dom/src/server/ReactPartialRenderer.js
+39
@@ -23,6 +23,7 @@ import {
23
warnAboutDeprecatedLifecycles,
24
enableSuspenseServerRenderer,
25
enableFlareAPI,
26
+ enableFundamentalAPI,
27
} from 'shared/ReactFeatureFlags';
28
29
import {
@@ -39,6 +40,7 @@ import {
40
REACT_LAZY_TYPE,
41
REACT_MEMO_TYPE,
42
REACT_EVENT_COMPONENT_TYPE,
43
+ REACT_FUNDAMENTAL_TYPE,
44
} from 'shared/ReactSymbols';
45
46
import {
@@ -1190,6 +1192,43 @@ class ReactDOMServerRenderer {
1192
);
1193
}
1194
// eslint-disable-next-line-no-fallthrough
1195
+ case REACT_FUNDAMENTAL_TYPE: {
1196
+ if (enableFundamentalAPI) {
1197
+ const fundamentalImpl = elementType.impl;
1198
+ const open = fundamentalImpl.getServerSideString(
1199
+ null,
1200
+ nextElement.props,
1201
+ );
1202
+ const getServerSideStringClose =
1203
+ fundamentalImpl.getServerSideStringClose;
1204
+ const close =
1205
+ getServerSideStringClose !== undefined
1206
+ ? getServerSideStringClose(null, nextElement.props)
1207
+ : '';
1208
+ const nextChildren =
1209
+ fundamentalImpl.reconcileChildren !== false
1210
+ ? toArray(((nextChild: any): ReactElement).props.children)
1211
+ : [];
1212
+ const frame: Frame = {
1213
+ type: null,
1214
+ domNamespace: parentNamespace,
1215
+ children: nextChildren,
1216
+ childIndex: 0,
1217
+ context: context,
1218
+ footer: close,
1219
+ };
1220
+ if (__DEV__) {
1221
+ ((frame: any): FrameDev).debugElementStack = [];
1222
+ }
1223
+ this.stack.push(frame);
1224
+ return open;
1225
+ }
1226
+ invariant(
1227
+ false,
1228
+ 'ReactDOMServer does not yet support the fundamental API.',
1229
+ );
1230
+ }
1231
+ // eslint-disable-next-line-no-fallthrough
1232
case REACT_LAZY_TYPE:
1233
invariant(
1234
false,
packages/react-native-renderer/src/ReactFabricHostConfig.js
+24
@@ -472,3 +472,27 @@ export function unmountEventComponent(
472
unmountEventResponder(eventComponentInstance);
473
}
474
}
475
+
476
+export function getFundamentalComponentInstance(fundamentalInstance) {
477
+ throw new Error('Not yet implemented.');
478
+}
479
+
480
+export function mountFundamentalComponent(fundamentalInstance) {
481
+ throw new Error('Not yet implemented.');
482
+}
483
+
484
+export function shouldUpdateFundamentalComponent(fundamentalInstance) {
485
+ throw new Error('Not yet implemented.');
486
+}
487
+
488
+export function updateFundamentalComponent(fundamentalInstance) {
489
+ throw new Error('Not yet implemented.');
490
+}
491
+
492
+export function unmountFundamentalComponent(fundamentalInstance) {
493
+ throw new Error('Not yet implemented.');
494
+}
495
+
496
+export function cloneFundamentalInstance(fundamentalInstance) {
497
+ throw new Error('Not yet implemented.');
498
+}
packages/react-native-renderer/src/ReactNativeHostConfig.js
+20
@@ -511,3 +511,23 @@ export function unmountEventComponent(
511
): void {
512
throw new Error('Not yet implemented.');
513
}
514
+
515
+export function getFundamentalComponentInstance(fundamentalInstance) {
516
+ throw new Error('Not yet implemented.');
517
+}
518
+
519
+export function mountFundamentalComponent(fundamentalInstance) {
520
+ throw new Error('Not yet implemented.');
521
+}
522
+
523
+export function shouldUpdateFundamentalComponent(fundamentalInstance) {
524
+ throw new Error('Not yet implemented.');
525
+}
526
+
527
+export function updateFundamentalComponent(fundamentalInstance) {
528
+ throw new Error('Not yet implemented.');
529
+}
530
+
531
+export function unmountFundamentalComponent(fundamentalInstance) {
532
+ throw new Error('Not yet implemented.');
533
+}
packages/react-noop-renderer/src/createReactNoop.js
+51
@@ -407,6 +407,57 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
407
unmountEventComponent(): void {
408
// NO-OP
409
},
410
+
411
+ getFundamentalComponentInstance(fundamentalInstance): Instance {
412
+ const {impl, props, state} = fundamentalInstance;
413
+ return impl.getInstance(null, props, state);
414
+ },
415
+
416
+ mountFundamentalComponent(fundamentalInstance): void {
417
+ const {impl, instance, props, state} = fundamentalInstance;
418
+ const onMount = impl.onUpdate;
419
+ if (onMount !== undefined) {
420
+ onMount(null, instance, props, state);
421
+ }
422
+ },
423
+
424
+ shouldUpdateFundamentalComponent(fundamentalInstance): boolean {
425
+ const {impl, instance, prevProps, props, state} = fundamentalInstance;
426
+ const shouldUpdate = impl.shouldUpdate;
427
+ if (shouldUpdate !== undefined) {
428
+ return shouldUpdate(null, instance, prevProps, props, state);
429
+ }
430
+ return true;
431
+ },
432
+
433
+ updateFundamentalComponent(fundamentalInstance): void {
434
+ const {impl, instance, prevProps, props, state} = fundamentalInstance;
435
+ const onUpdate = impl.onUpdate;
436
+ if (onUpdate !== undefined) {
437
+ onUpdate(null, instance, prevProps, props, state);
438
+ }
439
+ },
440
+
441
+ unmountFundamentalComponent(fundamentalInstance): void {
442
+ const {impl, instance, props, state} = fundamentalInstance;
443
+ const onUnmount = impl.onUnmount;
444
+ if (onUnmount !== undefined) {
445
+ onUnmount(null, instance, props, state);
446
+ }
447
+ },
448
+
449
+ cloneFundamentalInstance(fundamentalInstance): Instance {
450
+ const instance = fundamentalInstance.instance;
451
+ return {
452
+ children: [],
453
+ text: instance.text,
454
+ type: instance.type,
455
+ prop: instance.prop,
456
+ id: instance.id,
457
+ context: instance.context,
458
+ hidden: instance.hidden,
459
+ };
460
+ },
461
};
462
463
const hostConfig = useMutation
packages/react-reconciler/src/ReactFiber.js
+34
-2
@@ -13,6 +13,7 @@ import type {
13
ReactPortal,
14
RefObject,
15
ReactEventComponent,
16
+ ReactFundamentalComponent,
17
} from 'shared/ReactTypes';
18
import type {RootTag} from 'shared/ReactRootTags';
19
import type {WorkTag} from 'shared/ReactWorkTags';
@@ -26,7 +27,11 @@ import type {ReactEventComponentInstance} from 'shared/ReactTypes';
27
28
import invariant from 'shared/invariant';
29
import warningWithoutStack from 'shared/warningWithoutStack';
29
-import {enableProfilerTimer, enableFlareAPI} from 'shared/ReactFeatureFlags';
30
+import {
31
+ enableProfilerTimer,
32
+ enableFlareAPI,
33
+ enableFundamentalAPI,
34
+} from 'shared/ReactFeatureFlags';
35
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
36
import {ConcurrentRoot, BatchedRoot} from 'shared/ReactRootTags';
37
import {
@@ -49,6 +54,7 @@ import {
54
SimpleMemoComponent,
55
LazyComponent,
56
EventComponent,
57
+ FundamentalComponent,
58
} from 'shared/ReactWorkTags';
59
import getComponentName from 'shared/getComponentName';
60
@@ -79,6 +85,7 @@ import {
85
REACT_MEMO_TYPE,
86
REACT_LAZY_TYPE,
87
REACT_EVENT_COMPONENT_TYPE,
88
+ REACT_FUNDAMENTAL_TYPE,
89
} from 'shared/ReactSymbols';
90
91
let hasBadMapPolyfill;
@@ -663,6 +670,17 @@ export function createFiberFromTypeAndProps(
670
);
671
}
672
break;
673
+ case REACT_FUNDAMENTAL_TYPE:
674
+ if (enableFundamentalAPI) {
675
+ return createFiberFromFundamental(
676
+ type,
677
+ pendingProps,
678
+ mode,
679
+ expirationTime,
680
+ key,
681
+ );
682
+ }
683
+ break;
684
}
685
}
686
let info = '';
@@ -742,7 +760,7 @@ export function createFiberFromFragment(
760
}
761
762
export function createFiberFromEventComponent(
745
- eventComponent: ReactEventComponent<any>,
763
+ eventComponent: ReactEventComponent<any, any, any>,
764
pendingProps: any,
765
mode: TypeOfMode,
766
expirationTime: ExpirationTime,
@@ -755,6 +773,20 @@ export function createFiberFromEventComponent(
773
return fiber;
774
}
775
776
+export function createFiberFromFundamental(
777
+ fundamentalComponent: ReactFundamentalComponent<any, any>,
778
+ pendingProps: any,
779
+ mode: TypeOfMode,
780
+ expirationTime: ExpirationTime,
781
+ key: null | string,
782
+): Fiber {
783
+ const fiber = createFiber(FundamentalComponent, pendingProps, key, mode);
784
+ fiber.elementType = fundamentalComponent;
785
+ fiber.type = fundamentalComponent;
786
+ fiber.expirationTime = expirationTime;
787
+ return fiber;
788
+}
789
+
790
function createFiberFromProfiler(
791
pendingProps: any,
792
mode: TypeOfMode,
packages/react-reconciler/src/ReactFiberBeginWork.js
+34
-1
@@ -42,6 +42,7 @@ import {
42
LazyComponent,
43
IncompleteClassComponent,
44
EventComponent,
45
+ FundamentalComponent,
46
} from 'shared/ReactWorkTags';
47
import {
48
NoEffect,
@@ -61,6 +62,7 @@ import {
62
enableSchedulerTracing,
63
enableSuspenseServerRenderer,
64
enableFlareAPI,
65
+ enableFundamentalAPI,
66
} from 'shared/ReactFeatureFlags';
67
import invariant from 'shared/invariant';
68
import shallowEqual from 'shared/shallowEqual';
@@ -2479,7 +2481,7 @@ function updateContextConsumer(
2481
2482
function updateEventComponent(current, workInProgress, renderExpirationTime) {
2483
const nextProps = workInProgress.pendingProps;
2482
- let nextChildren = nextProps.children;
2484
+ const nextChildren = nextProps.children;
2485
2486
reconcileChildren(
2487
current,
@@ -2491,6 +2493,27 @@ function updateEventComponent(current, workInProgress, renderExpirationTime) {
2493
return workInProgress.child;
2494
}
2495
2496
+function updateFundamentalComponent(
2497
+ current,
2498
+ workInProgress,
2499
+ renderExpirationTime,
2500
+) {
2501
+ const fundamentalImpl = workInProgress.type.impl;
2502
+ if (fundamentalImpl.reconcileChildren === false) {
2503
+ return null;
2504
+ }
2505
+ const nextProps = workInProgress.pendingProps;
2506
+ const nextChildren = nextProps.children;
2507
+
2508
+ reconcileChildren(
2509
+ current,
2510
+ workInProgress,
2511
+ nextChildren,
2512
+ renderExpirationTime,
2513
+ );
2514
+ return workInProgress.child;
2515
+}
2516
+
2517
export function markWorkInProgressReceivedUpdate() {
2518
didReceiveUpdate = true;
2519
}
@@ -2980,6 +3003,16 @@ function beginWork(
3003
}
3004
break;
3005
}
3006
+ case FundamentalComponent: {
3007
+ if (enableFundamentalAPI) {
3008
+ return updateFundamentalComponent(
3009
+ current,
3010
+ workInProgress,
3011
+ renderExpirationTime,
3012
+ );
3013
+ }
3014
+ break;
3015
+ }
3016
}
3017
invariant(
3018
false,
packages/react-reconciler/src/ReactFiberCommitWork.js
+61
-9
@@ -29,6 +29,7 @@ import {
29
enableProfilerTimer,
30
enableSuspenseServerRenderer,
31
enableFlareAPI,
32
+ enableFundamentalAPI,
33
} from 'shared/ReactFeatureFlags';
34
import {
35
FunctionComponent,
@@ -46,6 +47,7 @@ import {
47
SimpleMemoComponent,
48
EventComponent,
49
SuspenseListComponent,
50
+ FundamentalComponent,
51
} from 'shared/ReactWorkTags';
52
import {
53
invokeGuardedCallback,
@@ -94,6 +96,8 @@ import {
96
unhideTextInstance,
97
unmountEventComponent,
98
mountEventComponent,
99
+ unmountFundamentalComponent,
100
+ updateFundamentalComponent,
101
} from './ReactFiberHostConfig';
102
import {
103
captureCommitPhaseError,
@@ -590,6 +594,7 @@ function commitLifeCycles(
594
case SuspenseComponent:
595
case SuspenseListComponent:
596
case IncompleteClassComponent:
597
+ case FundamentalComponent:
598
return;
599
case EventComponent: {
600
if (enableFlareAPI) {
@@ -757,6 +762,16 @@ function commitUnmount(current: Fiber): void {
762
unmountEventComponent(eventComponentInstance);
763
current.stateNode = null;
764
}
765
+ break;
766
+ }
767
+ case FundamentalComponent: {
768
+ if (enableFundamentalAPI) {
769
+ const fundamentalInstance = current.stateNode;
770
+ if (fundamentalInstance !== null) {
771
+ unmountFundamentalComponent(fundamentalInstance);
772
+ current.stateNode = null;
773
+ }
774
+ }
775
}
776
}
777
}
@@ -838,6 +853,7 @@ function commitContainer(finishedWork: Fiber) {
853
case ClassComponent:
854
case HostComponent:
855
case HostText:
856
+ case FundamentalComponent:
857
case EventComponent: {
858
return;
859
}
@@ -942,20 +958,26 @@ function commitPlacement(finishedWork: Fiber): void {
958
// Note: these two variables *must* always be updated together.
959
let parent;
960
let isContainer;
945
-
961
+ const parentStateNode = parentFiber.stateNode;
962
switch (parentFiber.tag) {
963
case HostComponent:
948
- parent = parentFiber.stateNode;
964
+ parent = parentStateNode;
965
isContainer = false;
966
break;
967
case HostRoot:
952
- parent = parentFiber.stateNode.containerInfo;
968
+ parent = parentStateNode.containerInfo;
969
isContainer = true;
970
break;
971
case HostPortal:
956
- parent = parentFiber.stateNode.containerInfo;
972
+ parent = parentStateNode.containerInfo;
973
isContainer = true;
974
break;
975
+ case FundamentalComponent:
976
+ if (enableFundamentalAPI) {
977
+ parent = parentStateNode.instance;
978
+ isContainer = false;
979
+ }
980
+ // eslint-disable-next-line-no-fallthrough
981
default:
982
invariant(
983
false,
@@ -975,8 +997,9 @@ function commitPlacement(finishedWork: Fiber): void {
997
// children to find all the terminal nodes.
998
let node: Fiber = finishedWork;
999
while (true) {
978
- if (node.tag === HostComponent || node.tag === HostText) {
979
- const stateNode = node.stateNode;
1000
+ const isHost = node.tag === HostComponent || node.tag === HostText;
1001
+ if (isHost || node.tag === FundamentalComponent) {
1002
+ const stateNode = isHost ? node.stateNode : node.stateNode.instance;
1003
if (before) {
1004
if (isContainer) {
1005
insertInContainerBefore(parent, stateNode, before);
@@ -1035,19 +1058,25 @@ function unmountHostComponents(current): void {
1058
'Expected to find a host parent. This error is likely caused by ' +
1059
'a bug in React. Please file an issue.',
1060
);
1061
+ const parentStateNode = parent.stateNode;
1062
switch (parent.tag) {
1063
case HostComponent:
1040
- currentParent = parent.stateNode;
1064
+ currentParent = parentStateNode;
1065
currentParentIsContainer = false;
1066
break findParent;
1067
case HostRoot:
1044
- currentParent = parent.stateNode.containerInfo;
1068
+ currentParent = parentStateNode.containerInfo;
1069
currentParentIsContainer = true;
1070
break findParent;
1071
case HostPortal:
1048
- currentParent = parent.stateNode.containerInfo;
1072
+ currentParent = parentStateNode.containerInfo;
1073
currentParentIsContainer = true;
1074
break findParent;
1075
+ case FundamentalComponent:
1076
+ if (enableFundamentalAPI) {
1077
+ currentParent = parentStateNode.instance;
1078
+ currentParentIsContainer = false;
1079
+ }
1080
}
1081
parent = parent.return;
1082
}
@@ -1070,6 +1099,22 @@ function unmountHostComponents(current): void {
1099
);
1100
}
1101
// Don't visit children because we already visited them.
1102
+ } else if (node.tag === FundamentalComponent) {
1103
+ const fundamentalNode = node.stateNode.instance;
1104
+ commitNestedUnmounts(node);
1105
+ // After all the children have unmounted, it is now safe to remove the
1106
+ // node from the tree.
1107
+ if (currentParentIsContainer) {
1108
+ removeChildFromContainer(
1109
+ ((currentParent: any): Container),
1110
+ (fundamentalNode: Instance),
1111
+ );
1112
+ } else {
1113
+ removeChild(
1114
+ ((currentParent: any): Instance),
1115
+ (fundamentalNode: Instance),
1116
+ );
1117
+ }
1118
} else if (
1119
enableSuspenseServerRenderer &&
1120
node.tag === DehydratedSuspenseComponent
@@ -1243,6 +1288,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1288
case EventComponent: {
1289
return;
1290
}
1291
+ case FundamentalComponent: {
1292
+ if (enableFundamentalAPI) {
1293
+ const fundamentalInstance = finishedWork.stateNode;
1294
+ updateFundamentalComponent(fundamentalInstance);
1295
+ }
1296
+ return;
1297
+ }
1298
default: {
1299
invariant(
1300
false,
packages/react-reconciler/src/ReactFiberCompleteWork.js
+82
-1
@@ -17,7 +17,10 @@ import type {
17
Container,
18
ChildSet,
19
} from './ReactFiberHostConfig';
20
-import type {ReactEventComponentInstance} from 'shared/ReactTypes';
20
+import type {
21
+ ReactEventComponentInstance,
22
+ ReactFundamentalComponentInstance,
23
+} from 'shared/ReactTypes';
24
import type {
25
SuspenseState,
26
SuspenseListRenderState,
@@ -48,6 +51,7 @@ import {
51
LazyComponent,
52
IncompleteClassComponent,
53
EventComponent,
54
+ FundamentalComponent,
55
} from 'shared/ReactWorkTags';
56
import {NoMode, BatchedMode} from './ReactTypeOfMode';
57
import {
@@ -75,6 +79,10 @@ import {
79
appendChildToContainerChildSet,
80
finalizeContainerChildren,
81
updateEventComponent,
82
+ getFundamentalComponentInstance,
83
+ mountFundamentalComponent,
84
+ cloneFundamentalInstance,
85
+ shouldUpdateFundamentalComponent,
86
} from './ReactFiberHostConfig';
87
import {
88
getRootHostContainer,
@@ -109,6 +117,7 @@ import {
117
enableSchedulerTracing,
118
enableSuspenseServerRenderer,
119
enableFlareAPI,
120
+ enableFundamentalAPI,
121
} from 'shared/ReactFeatureFlags';
122
import {
123
markSpawnedWork,
@@ -117,6 +126,7 @@ import {
126
renderHasNotSuspendedYet,
127
} from './ReactFiberWorkLoop';
128
import {createEventComponentInstance} from './ReactFiberEvents';
129
+import {createFundamentalStateInstance} from './ReactFiberFundamental';
130
import {Never} from './ReactFiberExpirationTime';
131
import {resetChildFibers} from './ReactChildFiber';
132
@@ -149,6 +159,8 @@ if (supportsMutation) {
159
while (node !== null) {
160
if (node.tag === HostComponent || node.tag === HostText) {
161
appendInitialChild(parent, node.stateNode);
162
+ } else if (node.tag === FundamentalComponent) {
163
+ appendInitialChild(parent, node.stateNode.instance);
164
} else if (node.tag === HostPortal) {
165
// If we have a portal child, then we don't want to traverse
166
// down its children. Instead, we'll get insertions from each child in
@@ -258,6 +270,15 @@ if (supportsMutation) {
270
instance = cloneHiddenTextInstance(instance, text, node);
271
}
272
appendInitialChild(parent, instance);
273
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
274
+ let instance = node.stateNode.instance;
275
+ if (needsVisibilityToggle && isHidden) {
276
+ // This child is inside a timed out tree. Hide it.
277
+ const props = node.memoizedProps;
278
+ const type = node.type;
279
+ instance = cloneHiddenInstance(instance, type, props, node);
280
+ }
281
+ appendInitialChild(parent, instance);
282
} else if (node.tag === HostPortal) {
283
// If we have a portal child, then we don't want to traverse
284
// down its children. Instead, we'll get insertions from each child in
@@ -343,6 +364,15 @@ if (supportsMutation) {
364
instance = cloneHiddenTextInstance(instance, text, node);
365
}
366
appendChildToContainerChildSet(containerChildSet, instance);
367
+ } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
368
+ let instance = node.stateNode.instance;
369
+ if (needsVisibilityToggle && isHidden) {
370
+ // This child is inside a timed out tree. Hide it.
371
+ const props = node.memoizedProps;
372
+ const type = node.type;
373
+ instance = cloneHiddenInstance(instance, type, props, node);
374
+ }
375
+ appendChildToContainerChildSet(containerChildSet, instance);
376
} else if (node.tag === HostPortal) {
377
// If we have a portal child, then we don't want to traverse
378
// down its children. Instead, we'll get insertions from each child in
@@ -1139,6 +1169,57 @@ function completeWork(
1169
}
1170
break;
1171
}
1172
+ case FundamentalComponent: {
1173
+ if (enableFundamentalAPI) {
1174
+ const fundamentalImpl = workInProgress.type.impl;
1175
+ let fundamentalInstance: ReactFundamentalComponentInstance<
1176
+ any,
1177
+ any,
1178
+ > | null =
1179
+ workInProgress.stateNode;
1180
+
1181
+ if (fundamentalInstance === null) {
1182
+ const getInitialState = fundamentalImpl.getInitialState;
1183
+ let fundamentalState;
1184
+ if (getInitialState !== undefined) {
1185
+ fundamentalState = getInitialState(newProps);
1186
+ }
1187
+ fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
1188
+ workInProgress,
1189
+ newProps,
1190
+ fundamentalImpl,
1191
+ fundamentalState || {},
1192
+ );
1193
+ const instance = ((getFundamentalComponentInstance(
1194
+ fundamentalInstance,
1195
+ ): any): Instance);
1196
+ fundamentalInstance.instance = instance;
1197
+ if (fundamentalImpl.reconcileChildren === false) {
1198
+ return null;
1199
+ }
1200
+ appendAllChildren(instance, workInProgress, false, false);
1201
+ mountFundamentalComponent(fundamentalInstance);
1202
+ } else {
1203
+ // We fire update in commit phase
1204
+ const prevProps = fundamentalInstance.props;
1205
+ fundamentalInstance.prevProps = prevProps;
1206
+ fundamentalInstance.props = newProps;
1207
+ fundamentalInstance.currentFiber = workInProgress;
1208
+ if (supportsPersistence) {
1209
+ const instance = cloneFundamentalInstance(fundamentalInstance);
1210
+ fundamentalInstance.instance = instance;
1211
+ appendAllChildren(instance, workInProgress, false, false);
1212
+ }
1213
+ const shouldUpdate = shouldUpdateFundamentalComponent(
1214
+ fundamentalInstance,
1215
+ );
1216
+ if (shouldUpdate) {
1217
+ markUpdate(workInProgress);
1218
+ }
1219
+ }
1220
+ }
1221
+ break;
1222
+ }
1223
default:
1224
invariant(
1225
false,
packages/react-reconciler/src/ReactFiberFundamental.js
new
+30
@@ -0,0 +1,30 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {Fiber} from './ReactFiber';
11
+import type {
12
+ ReactFundamentalImpl,
13
+ ReactFundamentalComponentInstance,
14
+} from 'shared/ReactTypes';
15
+
16
+export function createFundamentalStateInstance<C, H>(
17
+ currentFiber: Fiber,
18
+ props: Object,
19
+ impl: ReactFundamentalImpl<C, H>,
20
+ state: Object,
21
+): ReactFundamentalComponentInstance<C, H> {
22
+ return {
23
+ currentFiber,
24
+ impl,
25
+ instance: null,
26
+ prevProps: null,
27
+ props,
28
+ state,
29
+ };
30
+}
packages/react-reconciler/src/ReactFiberReconciler.js
+4
@@ -16,6 +16,7 @@ import type {
16
Container,
17
PublicInstance,
18
} from './ReactFiberHostConfig';
19
+import {FundamentalComponent} from 'shared/ReactWorkTags';
20
import type {ReactNodeList} from 'shared/ReactTypes';
21
import type {ExpirationTime} from './ReactFiberExpirationTime';
22
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
@@ -374,6 +375,9 @@ export function findHostInstanceWithNoPortals(
375
if (hostFiber === null) {
376
return null;
377
}
378
+ if (hostFiber.tag === FundamentalComponent) {
379
+ return hostFiber.stateNode.instance;
380
+ }
381
return hostFiber.stateNode;
382
}
383
packages/react-reconciler/src/ReactFiberTreeReflection.js
+6
-1
@@ -21,6 +21,7 @@ import {
21
HostRoot,
22
HostPortal,
23
HostText,
24
+ FundamentalComponent,
25
} from 'shared/ReactWorkTags';
26
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
27
@@ -277,7 +278,11 @@ export function findCurrentHostFiberWithNoPortals(parent: Fiber): Fiber | null {
278
// Next we'll drill down this component to find the first HostComponent/Text.
279
let node: Fiber = currentParent;
280
while (true) {
280
- if (node.tag === HostComponent || node.tag === HostText) {
281
+ if (
282
+ node.tag === HostComponent ||
283
+ node.tag === HostText ||
284
+ node.tag === FundamentalComponent
285
+ ) {
286
return node;
287
} else if (node.child && node.tag !== HostPortal) {
288
node.child.return = node;
packages/react-reconciler/src/__tests__/ReactFiberFundamental-test.internal.js
new
+225
@@ -0,0 +1,225 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+let React;
13
+let ReactNoop;
14
+let Scheduler;
15
+let ReactFeatureFlags;
16
+let ReactTestRenderer;
17
+let ReactDOM;
18
+let ReactDOMServer;
19
+
20
+function createReactFundamentalComponent(fundamentalImpl) {
21
+ return React.unstable_createFundamental(fundamentalImpl);
22
+}
23
+
24
+function init() {
25
+ jest.resetModules();
26
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
27
+ ReactFeatureFlags.enableFundamentalAPI = true;
28
+ React = require('react');
29
+ Scheduler = require('scheduler');
30
+}
31
+
32
+function initNoopRenderer() {
33
+ init();
34
+ ReactNoop = require('react-noop-renderer');
35
+}
36
+
37
+function initTestRenderer() {
38
+ init();
39
+ ReactTestRenderer = require('react-test-renderer');
40
+}
41
+
42
+function initReactDOM() {
43
+ init();
44
+ ReactDOM = require('react-dom');
45
+}
46
+
47
+function initReactDOMServer() {
48
+ init();
49
+ ReactDOMServer = require('react-dom/server');
50
+}
51
+
52
+describe('ReactFiberFundamental', () => {
53
+ describe('NoopRenderer', () => {
54
+ beforeEach(() => {
55
+ initNoopRenderer();
56
+ });
57
+
58
+ it('should render a simple fundamental component with a single child', () => {
59
+ const FundamentalComponent = createReactFundamentalComponent({
60
+ reconcileChildren: true,
61
+ getInstance(context, props, state) {
62
+ const instance = {
63
+ children: [],
64
+ text: null,
65
+ type: 'test',
66
+ };
67
+ return instance;
68
+ },
69
+ });
70
+
71
+ const Test = ({children}) => (
72
+ <FundamentalComponent>{children}</FundamentalComponent>
73
+ );
74
+
75
+ ReactNoop.render(<Test>Hello world</Test>);
76
+ expect(Scheduler).toFlushWithoutYielding();
77
+ expect(ReactNoop).toMatchRenderedOutput(<test>Hello world</test>);
78
+ ReactNoop.render(<Test>Hello world again</Test>);
79
+ expect(Scheduler).toFlushWithoutYielding();
80
+ expect(ReactNoop).toMatchRenderedOutput(<test>Hello world again</test>);
81
+ ReactNoop.render(null);
82
+ expect(Scheduler).toFlushWithoutYielding();
83
+ expect(ReactNoop).toMatchRenderedOutput(null);
84
+ });
85
+ });
86
+
87
+ describe('NoopTestRenderer', () => {
88
+ beforeEach(() => {
89
+ initTestRenderer();
90
+ });
91
+
92
+ it('should render a simple fundamental component with a single child', () => {
93
+ const FundamentalComponent = createReactFundamentalComponent({
94
+ reconcileChildren: true,
95
+ getInstance(context, props, state) {
96
+ const instance = {
97
+ children: [],
98
+ props,
99
+ type: 'test',
100
+ tag: 'INSTANCE',
101
+ };
102
+ return instance;
103
+ },
104
+ });
105
+
106
+ const Test = ({children}) => (
107
+ <FundamentalComponent>{children}</FundamentalComponent>
108
+ );
109
+
110
+ const root = ReactTestRenderer.create(null);
111
+ root.update(<Test>Hello world</Test>);
112
+ expect(Scheduler).toFlushWithoutYielding();
113
+ expect(root).toMatchRenderedOutput(<test>Hello world</test>);
114
+ root.update(<Test>Hello world again</Test>);
115
+ expect(Scheduler).toFlushWithoutYielding();
116
+ expect(root).toMatchRenderedOutput(<test>Hello world again</test>);
117
+ root.update(null);
118
+ expect(Scheduler).toFlushWithoutYielding();
119
+ expect(root).toMatchRenderedOutput(null);
120
+ });
121
+ });
122
+
123
+ describe('ReactDOM', () => {
124
+ beforeEach(() => {
125
+ initReactDOM();
126
+ });
127
+
128
+ it('should render a simple fundamental component with a single child', () => {
129
+ const FundamentalComponent = createReactFundamentalComponent({
130
+ reconcileChildren: true,
131
+ getInstance(context, props, state) {
132
+ const instance = document.createElement('div');
133
+ return instance;
134
+ },
135
+ });
136
+
137
+ const Test = ({children}) => (
138
+ <FundamentalComponent>{children}</FundamentalComponent>
139
+ );
140
+
141
+ const container = document.createElement('div');
142
+ ReactDOM.render(<Test>Hello world</Test>, container);
143
+ expect(Scheduler).toFlushWithoutYielding();
144
+ expect(container.innerHTML).toBe('<div>Hello world</div>');
145
+ ReactDOM.render(<Test>Hello world again</Test>, container);
146
+ expect(Scheduler).toFlushWithoutYielding();
147
+ expect(container.innerHTML).toBe('<div>Hello world again</div>');
148
+ ReactDOM.render(null, container);
149
+ expect(Scheduler).toFlushWithoutYielding();
150
+ expect(container.innerHTML).toBe('');
151
+ });
152
+
153
+ it('should render a simple fundamental component without reconcileChildren', () => {
154
+ const FundamentalComponent = createReactFundamentalComponent({
155
+ reconcileChildren: false,
156
+ getInstance(context, props, state) {
157
+ const instance = document.createElement('div');
158
+ instance.textContent = 'Hello world';
159
+ return instance;
160
+ },
161
+ });
162
+
163
+ const Test = () => <FundamentalComponent />;
164
+
165
+ const container = document.createElement('div');
166
+ ReactDOM.render(<Test />, container);
167
+ expect(Scheduler).toFlushWithoutYielding();
168
+ expect(container.innerHTML).toBe('<div>Hello world</div>');
169
+ // Children should be ignored
170
+ ReactDOM.render(<Test>Hello world again</Test>, container);
171
+ expect(Scheduler).toFlushWithoutYielding();
172
+ expect(container.innerHTML).toBe('<div>Hello world</div>');
173
+ ReactDOM.render(null, container);
174
+ expect(Scheduler).toFlushWithoutYielding();
175
+ expect(container.innerHTML).toBe('');
176
+ });
177
+ });
178
+
179
+ describe('ReactDOMServer', () => {
180
+ beforeEach(() => {
181
+ initReactDOMServer();
182
+ });
183
+
184
+ it('should render a simple fundamental component with a single child', () => {
185
+ const getInstance = jest.fn();
186
+ const FundamentalComponent = createReactFundamentalComponent({
187
+ reconcileChildren: true,
188
+ getInstance,
189
+ getServerSideString(context, props) {
190
+ return `<div>`;
191
+ },
192
+ getServerSideStringClose(context, props) {
193
+ return `</div>`;
194
+ },
195
+ });
196
+
197
+ const Test = ({children}) => (
198
+ <FundamentalComponent>{children}</FundamentalComponent>
199
+ );
200
+
201
+ expect(getInstance).not.toBeCalled();
202
+ let output = ReactDOMServer.renderToString(<Test>Hello world</Test>);
203
+ expect(output).toBe('<div>Hello world</div>');
204
+ output = ReactDOMServer.renderToString(<Test>Hello world again</Test>);
205
+ expect(output).toBe('<div>Hello world again</div>');
206
+ });
207
+
208
+ it('should render a simple fundamental component without reconcileChildren', () => {
209
+ const FundamentalComponent = createReactFundamentalComponent({
210
+ reconcileChildren: false,
211
+ getServerSideString(context, props) {
212
+ return `<div>Hello world</div>`;
213
+ },
214
+ });
215
+
216
+ const Test = () => <FundamentalComponent />;
217
+
218
+ let output = ReactDOMServer.renderToString(<Test />);
219
+ expect(output).toBe('<div>Hello world</div>');
220
+ // Children should be ignored
221
+ output = ReactDOMServer.renderToString(<Test>Hello world again</Test>);
222
+ expect(output).toBe('<div>Hello world</div>');
223
+ });
224
+ });
225
+});
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+11
-1
@@ -65,7 +65,12 @@ export const supportsPersistence = $$$hostConfig.supportsPersistence;
65
export const supportsHydration = $$$hostConfig.supportsHydration;
66
export const mountEventComponent = $$$hostConfig.mountEventComponent;
67
export const updateEventComponent = $$$hostConfig.updateEventComponent;
68
-export const handleEventTarget = $$$hostConfig.handleEventTarget;
68
+export const getFundamentalComponentInstance =
69
+ $$$hostConfig.getFundamentalComponentInstance;
70
+export const mountFundamentalComponent =
71
+ $$$hostConfig.mountFundamentalComponent;
72
+export const shouldUpdateFundamentalComponent =
73
+ $$$hostConfig.shouldUpdateFundamentalComponent;
74
75
// -------------------
76
// Mutation
@@ -87,6 +92,10 @@ export const unhideInstance = $$$hostConfig.unhideInstance;
92
export const unhideTextInstance = $$$hostConfig.unhideTextInstance;
93
export const unmountEventComponent = $$$hostConfig.unmountEventComponent;
94
export const commitEventTarget = $$$hostConfig.commitEventTarget;
95
+export const updateFundamentalComponent =
96
+ $$$hostConfig.updateFundamentalComponent;
97
+export const unmountFundamentalComponent =
98
+ $$$hostConfig.unmountFundamentalComponent;
99
100
// -------------------
101
// Persistence
@@ -101,6 +110,7 @@ export const finalizeContainerChildren =
110
export const replaceContainerChildren = $$$hostConfig.replaceContainerChildren;
111
export const cloneHiddenInstance = $$$hostConfig.cloneHiddenInstance;
112
export const cloneHiddenTextInstance = $$$hostConfig.cloneHiddenTextInstance;
113
+export const cloneFundamentalInstance = $$$hostConfig.cloneInstance;
114
115
// -------------------
116
// Hydration
packages/react-test-renderer/src/ReactTestHostConfig.js
+50
-1
@@ -9,7 +9,10 @@
9
10
import warning from 'shared/warning';
11
12
-import type {ReactEventComponentInstance} from 'shared/ReactTypes';
12
+import type {
13
+ ReactEventComponentInstance,
14
+ ReactFundamentalComponentInstance,
15
+} from 'shared/ReactTypes';
16
17
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
18
@@ -302,3 +305,49 @@ export function unmountEventComponent(
305
): void {
306
// noop
307
}
308
+
309
+export function getFundamentalComponentInstance(fundamentalInstance): Instance {
310
+ const {impl, props, state} = fundamentalInstance;
311
+ return impl.getInstance(null, props, state);
312
+}
313
+
314
+export function mountFundamentalComponent(
315
+ fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
316
+): void {
317
+ const {impl, instance, props, state} = fundamentalInstance;
318
+ const onMount = impl.onMount;
319
+ if (onMount !== undefined) {
320
+ onMount(null, instance, props, state);
321
+ }
322
+}
323
+
324
+export function shouldUpdateFundamentalComponent(
325
+ fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
326
+): boolean {
327
+ const {impl, prevProps, props, state} = fundamentalInstance;
328
+ const shouldUpdate = impl.shouldUpdate;
329
+ if (shouldUpdate !== undefined) {
330
+ return shouldUpdate(null, prevProps, props, state);
331
+ }
332
+ return true;
333
+}
334
+
335
+export function updateFundamentalComponent(
336
+ fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
337
+): void {
338
+ const {impl, instance, prevProps, props, state} = fundamentalInstance;
339
+ const onUpdate = impl.onUpdate;
340
+ if (onUpdate !== undefined) {
341
+ onUpdate(null, instance, prevProps, props, state);
342
+ }
343
+}
344
+
345
+export function unmountFundamentalComponent(
346
+ fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
347
+): void {
348
+ const {impl, instance, props, state} = fundamentalInstance;
349
+ const onUnmount = impl.onUnmount;
350
+ if (onUnmount !== undefined) {
351
+ onUnmount(null, instance, props, state);
352
+ }
353
+}
packages/react/src/React.js
+10
-1
@@ -52,7 +52,12 @@ import {
52
} from './ReactElementValidator';
53
import ReactSharedInternals from './ReactSharedInternals';
54
import createEvent from 'shared/createEventComponent';
55
-import {enableJSXTransformAPI, enableFlareAPI} from 'shared/ReactFeatureFlags';
55
+import createFundamental from 'shared/createFundamentalComponent';
56
+import {
57
+ enableJSXTransformAPI,
58
+ enableFlareAPI,
59
+ enableFundamentalAPI,
60
+} from 'shared/ReactFeatureFlags';
61
const React = {
62
Children: {
63
map,
@@ -105,6 +110,10 @@ if (enableFlareAPI) {
110
React.unstable_useEvent = useEvent;
111
}
112
113
+if (enableFundamentalAPI) {
114
+ React.unstable_createFundamental = createFundamental;
115
+}
116
+
117
// Note: some APIs are added with feature flags.
118
// Make sure that stable builds for open source
119
// don't modify the React object to avoid deopts.
packages/shared/HostConfigWithNoPersistence.js
+1
@@ -24,6 +24,7 @@ function shim(...args: any) {
24
// Persistence (when unsupported)
25
export const supportsPersistence = false;
26
export const cloneInstance = shim;
27
+export const cloneFundamentalInstance = shim;
28
export const createContainerChildSet = shim;
29
export const appendChildToContainerChildSet = shim;
30
export const finalizeContainerChildren = shim;
packages/shared/ReactDOMTypes.js
+6
@@ -8,6 +8,7 @@
8
*/
9
10
import type {
11
+ ReactFundamentalComponentInstance,
12
ReactEventResponder,
13
ReactEventComponentInstance,
14
EventPriority,
@@ -44,6 +45,11 @@ export type ReactDOMEventComponentInstance = ReactEventComponentInstance<
45
ReactDOMResponderContext,
46
>;
47
48
+export type ReactDOMFundamentalComponentInstance = ReactFundamentalComponentInstance<
49
+ any,
50
+ any,
51
+>;
52
+
53
export type ReactDOMResponderContext = {
54
dispatchEvent: (
55
eventObject: Object,
packages/shared/ReactFeatureFlags.js
+3
@@ -62,6 +62,9 @@ export const warnAboutDeprecatedSetNativeProps = false;
62
// Experimental React Flare event system and event components support.
63
export const enableFlareAPI = false;
64
65
+// Experimental Host Component support.
66
+export const enableFundamentalAPI = false;
67
+
68
// New API for JSX transforms to target - https://github.com/reactjs/rfcs/pull/107
69
export const enableJSXTransformAPI = false;
70
packages/shared/ReactSymbols.js
+3
@@ -54,6 +54,9 @@ export const REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4;
54
export const REACT_EVENT_COMPONENT_TYPE = hasSymbol
55
? Symbol.for('react.event_component')
56
: 0xead5;
57
+export const REACT_FUNDAMENTAL_TYPE = hasSymbol
58
+ ? Symbol.for('react.fundamental')
59
+ : 0xead6;
60
61
const MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
62
const FAUX_ITERATOR_SYMBOL = '@@iterator';
packages/shared/ReactTypes.js
+45
@@ -112,3 +112,48 @@ export opaque type EventPriority = 0 | 1 | 2;
112
export const DiscreteEvent: EventPriority = 0;
113
export const UserBlockingEvent: EventPriority = 1;
114
export const ContinuousEvent: EventPriority = 2;
115
+
116
+export type ReactFundamentalComponentInstance<C, H> = {|
117
+ currentFiber: mixed,
118
+ instance: mixed,
119
+ prevProps: null | Object,
120
+ props: Object,
121
+ impl: ReactFundamentalImpl<C, H>,
122
+ state: Object,
123
+|};
124
+
125
+export type ReactFundamentalImpl<C, H> = {
126
+ displayName: string,
127
+ reconcileChildren: boolean,
128
+ getInitialState?: (props: Object) => Object,
129
+ getInstance: (context: C, props: Object, state: Object) => H,
130
+ getServerSideString?: (context: C, props: Object) => string,
131
+ getServerSideStringClose?: (context: C, props: Object) => string,
132
+ onMount: (context: C, instance: mixed, props: Object, state: Object) => void,
133
+ shouldUpdate?: (
134
+ context: C,
135
+ prevProps: null | Object,
136
+ nextProps: Object,
137
+ state: Object,
138
+ ) => boolean,
139
+ onUpdate?: (
140
+ context: C,
141
+ instance: mixed,
142
+ prevProps: null | Object,
143
+ nextProps: Object,
144
+ state: Object,
145
+ ) => void,
146
+ onUnmount?: (
147
+ context: C,
148
+ instance: mixed,
149
+ props: Object,
150
+ state: Object,
151
+ ) => void,
152
+ onHydrate?: (context: C, props: Object, state: Object) => boolean,
153
+ onFocus?: (context: C, props: Object, state: Object) => boolean,
154
+};
155
+
156
+export type ReactFundamentalComponent<C, H> = {|
157
+ $$typeof: Symbol | number,
158
+ impl: ReactFundamentalImpl<C, H>,
159
+|};
packages/shared/ReactWorkTags.js
+1
-1
@@ -51,5 +51,5 @@ export const LazyComponent = 16;
51
export const IncompleteClassComponent = 17;
52
export const DehydratedSuspenseComponent = 18;
53
export const EventComponent = 19;
54
-export const EventTarget = 20;
54
+export const FundamentalComponent = 20;
55
export const SuspenseListComponent = 21;
packages/shared/createEventComponent.js
+2
-20
@@ -8,25 +8,7 @@
8
9
import type {ReactEventResponder, ReactEventComponent} from 'shared/ReactTypes';
10
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
11
-
12
-let hasBadMapPolyfill;
13
-
14
-if (__DEV__) {
15
- hasBadMapPolyfill = false;
16
- try {
17
- const frozenObject = Object.freeze({});
18
- const testMap = new Map([[frozenObject, null]]);
19
- const testSet = new Set([frozenObject]);
20
- // This is necessary for Rollup to not consider these unused.
21
- // https://github.com/rollup/rollup/issues/1771
22
- // TODO: we can remove these if Rollup fixes the bug.
23
- testMap.set(0, 0);
24
- testSet.add(0);
25
- } catch (e) {
26
- // TODO: Consider warning about bad polyfills
27
- hasBadMapPolyfill = true;
28
- }
29
-}
11
+import {hasBadMapPolyfill} from './hasBadMapPolyfill';
12
13
export default function createEventComponent<E, C>(
14
responder: ReactEventResponder<E, C>,
@@ -39,7 +21,7 @@ export default function createEventComponent<E, C>(
21
}
22
const eventComponent = {
23
$$typeof: REACT_EVENT_COMPONENT_TYPE,
42
- responder: responder,
24
+ responder,
25
};
26
if (__DEV__) {
27
Object.freeze(eventComponent);
packages/shared/createFundamentalComponent.js
new
+33
@@ -0,0 +1,33 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ * @flow
7
+ */
8
+
9
+import type {
10
+ ReactFundamentalImpl,
11
+ ReactFundamentalComponent,
12
+} from 'shared/ReactTypes';
13
+import {REACT_FUNDAMENTAL_TYPE} from 'shared/ReactSymbols';
14
+import {hasBadMapPolyfill} from './hasBadMapPolyfill';
15
+
16
+export default function createFundamentalComponent<C, H>(
17
+ impl: ReactFundamentalImpl<C, H>,
18
+): ReactFundamentalComponent<C, H> {
19
+ // We use responder as a Map key later on. When we have a bad
20
+ // polyfill, then we can't use it as a key as the polyfill tries
21
+ // to add a property to the object.
22
+ if (__DEV__ && !hasBadMapPolyfill) {
23
+ Object.freeze(impl);
24
+ }
25
+ const fundamantalComponent = {
26
+ $$typeof: REACT_FUNDAMENTAL_TYPE,
27
+ impl,
28
+ };
29
+ if (__DEV__) {
30
+ Object.freeze(fundamantalComponent);
31
+ }
32
+ return fundamantalComponent;
33
+}
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -32,6 +32,7 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
32
export const warnAboutDeprecatedLifecycles = true;
33
export const warnAboutDeprecatedSetNativeProps = true;
34
export const enableFlareAPI = false;
35
+export const enableFundamentalAPI = false;
36
export const enableJSXTransformAPI = false;
37
export const warnAboutMissingMockScheduler = true;
38
export const revertPassiveEffectsChange = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -27,6 +27,7 @@ export const warnAboutShorthandPropertyCollision = false;
27
export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
+export const enableFundamentalAPI = false;
31
export const enableJSXTransformAPI = false;
32
export const warnAboutMissingMockScheduler = false;
33
export const revertPassiveEffectsChange = false;
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -27,6 +27,7 @@ export const warnAboutShorthandPropertyCollision = false;
27
export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
+export const enableFundamentalAPI = false;
31
export const enableJSXTransformAPI = false;
32
export const warnAboutMissingMockScheduler = true;
33
export const revertPassiveEffectsChange = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -27,6 +27,7 @@ export const warnAboutShorthandPropertyCollision = false;
27
export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const enableFlareAPI = false;
30
+export const enableFundamentalAPI = false;
31
export const enableJSXTransformAPI = false;
32
export const warnAboutMissingMockScheduler = false;
33
export const revertPassiveEffectsChange = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -28,6 +28,7 @@ export const enableSchedulerDebugging = false;
28
export const warnAboutDeprecatedSetNativeProps = false;
29
export const disableJavaScriptURLs = false;
30
export const enableFlareAPI = true;
31
+export const enableFundamentalAPI = false;
32
export const enableJSXTransformAPI = true;
33
export const warnAboutMissingMockScheduler = true;
34
export const enableUserBlockingEvents = false;
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -70,6 +70,8 @@ function updateFlagOutsideOfReactCallStack() {
70
71
export const enableFlareAPI = true;
72
73
+export const enableFundamentalAPI = false;
74
+
75
export const enableJSXTransformAPI = true;
76
77
export const warnAboutMissingMockScheduler = true;
packages/shared/hasBadMapPolyfill.js
new
+26
@@ -0,0 +1,26 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ * @flow
7
+ */
8
+
9
+export let hasBadMapPolyfill;
10
+
11
+if (__DEV__) {
12
+ hasBadMapPolyfill = false;
13
+ try {
14
+ const frozenObject = Object.freeze({});
15
+ const testMap = new Map([[frozenObject, null]]);
16
+ const testSet = new Set([frozenObject]);
17
+ // This is necessary for Rollup to not consider these unused.
18
+ // https://github.com/rollup/rollup/issues/1771
19
+ // TODO: we can remove these if Rollup fixes the bug.
20
+ testMap.set(0, 0);
21
+ testSet.add(0);
22
+ } catch (e) {
23
+ // TODO: Consider warning about bad polyfills
24
+ hasBadMapPolyfill = true;
25
+ }
26
+}
packages/shared/isValidElementType.js
+3
-1
@@ -20,6 +20,7 @@ import {
20
REACT_MEMO_TYPE,
21
REACT_LAZY_TYPE,
22
REACT_EVENT_COMPONENT_TYPE,
23
+ REACT_FUNDAMENTAL_TYPE,
24
} from 'shared/ReactSymbols';
25
26
export default function isValidElementType(type: mixed) {
@@ -40,6 +41,7 @@ export default function isValidElementType(type: mixed) {
41
type.$$typeof === REACT_PROVIDER_TYPE ||
42
type.$$typeof === REACT_CONTEXT_TYPE ||
43
type.$$typeof === REACT_FORWARD_REF_TYPE ||
43
- type.$$typeof === REACT_EVENT_COMPONENT_TYPE))
44
+ type.$$typeof === REACT_EVENT_COMPONENT_TYPE ||
45
+ type.$$typeof === REACT_FUNDAMENTAL_TYPE))
46
);
47
}
scripts/error-codes/codes.json
+2
-1
@@ -334,5 +334,6 @@
334
"333": "This should have a parent host component initialized. This error is likely caused by a bug in React. Please file an issue.",
335
"334": "accumulate(...): Accumulated items must not be null or undefined.",
336
"335": "ReactDOMServer does not yet support the event API.",
337
- "336": "The \"%s\" event responder cannot be used via the \"useEvent\" hook."
337
+ "336": "The \"%s\" event responder cannot be used via the \"useEvent\" hook.",
338
+ "337": "ReactDOMServer does not yet support the fundamental API."
339
}