[CS] Split Host Config Out into a Mutable or Immutable Mode (#11213)

* CS renderer

Because we didn't have enough RN experiments. I want to add one more.

* Split out hydration from the host config object

This makes it easier to do feature detection on the configuration.

* Move mutation host config to separate optional object

* Refs and life-cycles should happen even in immutable mode

* Unmount components even in non-mutation mode

This is the same as committing deletions but instead of finding host
components to delete, it only invokes componentWillUnmount and detaching
of refs.

* Add persistent updates API

This mode will use a clone based API instead of mutating host instances.

Needs implementation still.

It's awkward that there can be more than one child inserted into the root.
So we need a new API to create a "root" instance so that we can update it
atomically. Alternatively we could keep the mutable API for containers
and assume that most use cases would only have a single root.

* Package up CS renderer

* Fix reconciler package fixture
This commit is contained in:
Sebastian Markbåge
2017-10-13 14:29:59 -07:00
committed by GitHub
parent 339d6cb32b
commit 36a2afccc5
19 changed files with 1401 additions and 981 deletions

View File

@@ -115,28 +115,12 @@ var NoopRenderer = ReactFiberReconciler({
return UPDATE_SIGNAL;
},
commitMount(instance: Instance, type: string, newProps: Props): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props
): void {
instance.prop = newProps.prop;
},
shouldSetTextContent(type: string, props: Props): boolean {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
},
resetTextContent(instance: Instance): void {},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
},
@@ -155,21 +139,6 @@ var NoopRenderer = ReactFiberReconciler({
return inst;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
scheduleDeferredCallback(callback) {
if (scheduledCallback) {
throw new Error(
@@ -183,6 +152,39 @@ var NoopRenderer = ReactFiberReconciler({
prepareForCommit(): void {},
resetAfterCommit(): void {},
mutation: {
commitMount(instance: Instance, type: string, newProps: Props): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props
): void {
instance.prop = newProps.prop;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
resetTextContent(instance: Instance): void {},
},
});
var rootContainers = new Map();

View File

@@ -21,6 +21,7 @@ const Stats = require('./stats');
const syncReactDom = require('./sync').syncReactDom;
const syncReactNative = require('./sync').syncReactNative;
const syncReactNativeRT = require('./sync').syncReactNativeRT;
const syncReactNativeCS = require('./sync').syncReactNativeCS;
const Packaging = require('./packaging');
const Header = require('./header');
const closure = require('rollup-plugin-closure-compiler-js');
@@ -571,6 +572,7 @@ rimraf('build', () => {
Packaging.createFacebookWWWBuild,
Packaging.createReactNativeBuild,
Packaging.createReactNativeRTBuild,
Packaging.createReactNativeCSBuild,
];
for (const bundle of Bundles.bundles) {
tasks.push(
@@ -591,6 +593,9 @@ rimraf('build', () => {
tasks.push(() =>
syncReactNativeRT(join('build', 'react-native-rt'), syncFbsource)
);
tasks.push(() =>
syncReactNativeCS(join('build', 'react-native-cs'), syncFbsource)
);
} else if (syncWww) {
tasks.push(() => syncReactDom(join('build', 'facebook-www'), syncWww));
}

View File

@@ -298,7 +298,7 @@ const bundles = [
useFiber: true,
},
/******* React Native *******/
/******* React Native RT *******/
{
babelOpts: babelOptsReact,
bundleTypes: [RN_DEV, RN_PROD],
@@ -332,6 +332,32 @@ const bundles = [
useFiber: true,
},
/******* React Native CS *******/
{
babelOpts: babelOptsReact,
bundleTypes: [RN_DEV, RN_PROD],
config: {
destDir: 'build/',
moduleName: 'ReactNativeCSFiber',
sourceMap: false,
},
entry: 'src/renderers/native-cs/ReactNativeCSFiberEntry',
externals: ['prop-types/checkPropTypes'],
hasteName: 'ReactNativeCSFiber',
isRenderer: true,
label: 'native-cs-fiber',
manglePropertiesOnProd: false,
name: 'react-native-cs-renderer',
paths: [
'src/renderers/native-cs/**/*.js',
'src/renderers/shared/**/*.js',
'src/ReactVersion.js',
'src/shared/**/*.js',
],
useFiber: true,
},
/******* React Test Renderer *******/
{
babelOpts: babelOptsReact,

View File

@@ -33,6 +33,11 @@ const reactNativeRTSrcDependencies = [
'src/renderers/native-rt/ReactNativeRTTypes.js',
];
// these files need to be copied to the react-native-cs build
const reactNativeCSSrcDependencies = [
'src/renderers/native-cs/ReactNativeCSTypes.js',
];
function getPackageName(name) {
if (name.indexOf('/') !== -1) {
return name.split('/')[0];
@@ -81,6 +86,25 @@ function createReactNativeRTBuild() {
return Promise.all(promises);
}
function createReactNativeCSBuild() {
// create the react-native-cs folder for FB bundles
fs.mkdirSync(join('build', 'react-native-cs'));
// create the react-native-cs shims folder for FB shims
fs.mkdirSync(join('build', 'react-native-cs', 'shims'));
const to = join('build', 'react-native-cs', 'shims');
let promises = [];
// we also need to copy over some specific files from src
// defined in reactNativeCSSrcDependencies
for (const srcDependency of reactNativeCSSrcDependencies) {
promises.push(
asyncCopyTo(resolve(srcDependency), join(to, basename(srcDependency)))
);
}
return Promise.all(promises);
}
function createFacebookWWWBuild() {
// create the facebookWWW folder for FB bundles
fs.mkdirSync(join('build', facebookWWW));
@@ -191,4 +215,5 @@ module.exports = {
createFacebookWWWBuild,
createReactNativeBuild,
createReactNativeRTBuild,
createReactNativeCSBuild,
};

View File

@@ -25,28 +25,28 @@
"gzip": 6703
},
"react-dom.development.js (UMD_DEV)": {
"size": 630418,
"gzip": 145239
"size": 625837,
"gzip": 144320
},
"react-dom.production.min.js (UMD_PROD)": {
"size": 101638,
"gzip": 32128
"size": 100866,
"gzip": 31848
},
"react-dom.development.js (NODE_DEV)": {
"size": 592683,
"gzip": 136496
"size": 588106,
"gzip": 135609
},
"react-dom.production.min.js (NODE_PROD)": {
"size": 106507,
"gzip": 33538
"size": 105343,
"gzip": 33129
},
"ReactDOMFiber-dev.js (FB_DEV)": {
"size": 589582,
"gzip": 135915
"size": 584995,
"gzip": 135013
},
"ReactDOMFiber-prod.js (FB_PROD)": {
"size": 419983,
"gzip": 93676
"size": 414881,
"gzip": 92663
},
"react-dom-test-utils.development.js (NODE_DEV)": {
"size": 41660,
@@ -113,44 +113,44 @@
"gzip": 6214
},
"react-art.development.js (UMD_DEV)": {
"size": 376896,
"gzip": 83318
"size": 372576,
"gzip": 82452
},
"react-art.production.min.js (UMD_PROD)": {
"size": 83700,
"gzip": 25995
"size": 83057,
"gzip": 25828
},
"react-art.development.js (NODE_DEV)": {
"size": 301201,
"gzip": 64095
"size": 296909,
"gzip": 63228
},
"react-art.production.min.js (NODE_PROD)": {
"size": 53531,
"gzip": 16898
"size": 52508,
"gzip": 16427
},
"ReactARTFiber-dev.js (FB_DEV)": {
"size": 300073,
"gzip": 64131
"size": 295771,
"gzip": 63258
},
"ReactARTFiber-prod.js (FB_PROD)": {
"size": 224585,
"gzip": 46566
"size": 219800,
"gzip": 45628
},
"ReactNativeFiber-dev.js (RN_DEV)": {
"size": 283095,
"gzip": 49176
"size": 279764,
"gzip": 48665
},
"ReactNativeFiber-prod.js (RN_PROD)": {
"size": 221913,
"gzip": 38509
"size": 218506,
"gzip": 37873
},
"react-test-renderer.development.js (NODE_DEV)": {
"size": 304971,
"gzip": 64484
"size": 300619,
"gzip": 63624
},
"ReactTestRendererFiber-dev.js (FB_DEV)": {
"size": 303833,
"gzip": 64527
"size": 299471,
"gzip": 63657
},
"react-test-renderer-shallow.development.js (NODE_DEV)": {
"size": 9215,
@@ -161,8 +161,8 @@
"gzip": 2221
},
"react-noop-renderer.development.js (NODE_DEV)": {
"size": 292571,
"gzip": 61440
"size": 288214,
"gzip": 60549
},
"react-dom-server.development.js (UMD_DEV)": {
"size": 120897,
@@ -189,16 +189,16 @@
"gzip": 7520
},
"ReactNativeRTFiber-dev.js (RN_DEV)": {
"size": 215098,
"gzip": 36673
"size": 211687,
"gzip": 36145
},
"ReactNativeRTFiber-prod.js (RN_PROD)": {
"size": 163687,
"gzip": 27527
"size": 160200,
"gzip": 26880
},
"react-test-renderer.production.min.js (NODE_PROD)": {
"size": 55085,
"gzip": 17135
"size": 54076,
"gzip": 16776
},
"react-test-renderer-shallow.production.min.js (NODE_PROD)": {
"size": 4536,
@@ -209,12 +209,20 @@
"gzip": 4241
},
"react-reconciler.development.js (NODE_DEV)": {
"size": 279927,
"gzip": 58576
"size": 275518,
"gzip": 57698
},
"react-reconciler.production.min.js (NODE_PROD)": {
"size": 38630,
"gzip": 12165
"size": 37979,
"gzip": 11842
},
"ReactNativeCSFiber-dev.js (RN_DEV)": {
"size": 204077,
"gzip": 34318
},
"ReactNativeCSFiber-prod.js (RN_PROD)": {
"size": 155097,
"gzip": 25642
}
}
}

View File

@@ -388,20 +388,6 @@ class Text extends React.Component {
/** ART Renderer */
const ARTRenderer = ReactFiberReconciler({
appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
@@ -412,18 +398,6 @@ const ARTRenderer = ReactFiberReconciler({
child.inject(parentInstance);
},
commitTextUpdate(textInstance, oldText, newText) {
// Noop
},
commitMount(instance, type, newProps) {
// Noop
},
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
createInstance(type, props, internalInstanceHandle) {
let instance;
@@ -470,22 +444,6 @@ const ARTRenderer = ReactFiberReconciler({
return instance;
},
insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
prepareForCommit() {
// Noop
},
@@ -494,16 +452,6 @@ const ARTRenderer = ReactFiberReconciler({
return UPDATE_SIGNAL;
},
removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
resetAfterCommit() {
// Noop
},
@@ -535,6 +483,60 @@ const ARTRenderer = ReactFiberReconciler({
now: ReactDOMFrameScheduling.now,
useSyncScheduling: true,
mutation: {
appendChild(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
appendChildToContainer(parentInstance, child) {
if (child.parentNode === parentInstance) {
child.eject();
}
child.inject(parentInstance);
},
insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
insertInContainerBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
'ReactART: Can not insert node before itself',
);
child.injectBefore(beforeChild);
},
removeChild(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
removeChildFromContainer(parentInstance, child) {
destroyEventListeners(child);
child.eject();
},
commitTextUpdate(textInstance, oldText, newText) {
// Noop
},
commitMount(instance, type, newProps) {
// Noop
},
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
instance._applyProps(instance, newProps, oldProps);
},
},
});
/** API */

View File

@@ -317,34 +317,6 @@ var DOMRenderer = ReactFiberReconciler({
);
},
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
},
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
shouldSetTextContent(type: string, props: Props): boolean {
return (
type === 'textarea' ||
@@ -356,10 +328,6 @@ var DOMRenderer = ReactFiberReconciler({
);
},
resetTextContent(domElement: Instance): void {
domElement.textContent = '';
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
},
@@ -379,245 +347,287 @@ var DOMRenderer = ReactFiberReconciler({
return textNode;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
},
appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
parentInstance.appendChild(child);
},
appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, container);
} else {
container.appendChild(child);
}
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
},
insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
container.insertBefore(child, beforeChild);
}
},
removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
parentInstance.removeChild(child);
},
removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
},
now: ReactDOMFrameScheduling.now,
canHydrateInstance(
instance: Instance | TextInstance,
type: string,
props: Props,
): boolean {
return (
instance.nodeType === ELEMENT_NODE &&
type.toLowerCase() === instance.nodeName.toLowerCase()
);
},
mutation: {
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
},
canHydrateTextInstance(
instance: Instance | TextInstance,
text: string,
): boolean {
if (text === '') {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return false;
}
return instance.nodeType === TEXT_NODE;
},
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
getNextHydratableSibling(
instance: Instance | TextInstance,
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE
) {
node = node.nextSibling;
}
return (node: any);
},
resetTextContent(domElement: Instance): void {
domElement.textContent = '';
},
getFirstHydratableChild(
parentInstance: Container | Instance,
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE
) {
next = next.nextSibling;
}
return (next: any);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.nodeValue = newText;
},
hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
},
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
},
hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
},
didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
},
didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
},
didNotHydrateContainerInstance(
parentContainer: Container,
instance: Instance | TextInstance,
) {
if (__DEV__) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
appendChildToContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, container);
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
container.appendChild(child);
}
}
},
},
didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: Instance | TextInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
parentInstance.insertBefore(child, beforeChild);
},
insertInContainerBefore(
container: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).insertBefore(child, beforeChild);
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
container.insertBefore(child, beforeChild);
}
}
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.removeChild(child);
},
removeChildFromContainer(
container: Container,
child: Instance | TextInstance,
): void {
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
container.removeChild(child);
}
},
},
didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
},
hydration: {
canHydrateInstance(
instance: Instance | TextInstance,
type: string,
props: Props,
): boolean {
return (
instance.nodeType === ELEMENT_NODE &&
type.toLowerCase() === instance.nodeName.toLowerCase()
);
},
didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
},
canHydrateTextInstance(
instance: Instance | TextInstance,
text: string,
): boolean {
if (text === '') {
// Empty strings are not parsed by HTML so there won't be a correct match here.
return false;
}
return instance.nodeType === TEXT_NODE;
},
didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
},
getNextHydratableSibling(
instance: Instance | TextInstance,
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
while (
node &&
node.nodeType !== ELEMENT_NODE &&
node.nodeType !== TEXT_NODE
) {
node = node.nextSibling;
}
return (node: any);
},
didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
getFirstHydratableChild(
parentInstance: Container | Instance,
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
while (
next &&
next.nodeType !== ELEMENT_NODE &&
next.nodeType !== TEXT_NODE
) {
next = next.nextSibling;
}
return (next: any);
},
hydrateInstance(
instance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): null | Array<mixed> {
precacheFiberNode(internalInstanceHandle, instance);
// TODO: Possibly defer this until the commit phase where all the events
// get attached.
updateFiberProps(instance, props);
let parentNamespace: string;
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
return diffHydratedProperties(
instance,
type,
props,
parentNamespace,
rootContainerInstance,
);
},
hydrateTextInstance(
textInstance: TextInstance,
text: string,
internalInstanceHandle: Object,
): boolean {
precacheFiberNode(internalInstanceHandle, textInstance);
return diffHydratedText(textInstance, text);
},
didNotMatchHydratedContainerTextInstance(
parentContainer: Container,
textInstance: TextInstance,
text: string,
) {
if (__DEV__) {
warnForUnmatchedText(textInstance, text);
}
},
didNotMatchHydratedTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
textInstance: TextInstance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForUnmatchedText(textInstance, text);
}
},
didNotHydrateContainerInstance(
parentContainer: Container,
instance: Instance | TextInstance,
) {
if (__DEV__) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentContainer, (instance: any));
} else {
warnForDeletedHydratableText(parentContainer, (instance: any));
}
}
},
didNotHydrateInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
instance: Instance | TextInstance,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
if (instance.nodeType === 1) {
warnForDeletedHydratableElement(parentInstance, (instance: any));
} else {
warnForDeletedHydratableText(parentInstance, (instance: any));
}
}
},
didNotFindHydratableContainerInstance(
parentContainer: Container,
type: string,
props: Props,
) {
if (__DEV__) {
warnForInsertedHydratedElement(parentContainer, type, props);
}
},
didNotFindHydratableContainerTextInstance(
parentContainer: Container,
text: string,
) {
if (__DEV__) {
warnForInsertedHydratedText(parentContainer, text);
}
},
didNotFindHydratableInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
type: string,
props: Props,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedElement(parentInstance, type, props);
}
},
didNotFindHydratableTextInstance(
parentType: string,
parentProps: Props,
parentInstance: Instance,
text: string,
) {
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
warnForInsertedHydratedText(parentInstance, text);
}
},
},
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule ReactNativeCSFiberEntry
* @flow
*/
'use strict';
const ReactGenericBatching = require('ReactGenericBatching');
const ReactVersion = require('ReactVersion');
const {injectInternals} = require('ReactFiberDevToolsHook');
import type {ReactNativeCSType} from 'ReactNativeCSTypes';
const ReactFiberReconciler = require('ReactFiberReconciler');
const emptyObject = require('fbjs/lib/emptyObject');
export type Container = number;
export type Instance = number;
export type Props = Object;
export type TextInstance = number;
function processProps(instance: number, props: Props): Object {
const propsPayload = {};
for (var key in props) {
if (key === 'children') {
// Skip special case.
continue;
}
var value = props[key];
if (typeof value === 'function') {
value = {
style: 'rt-event',
event: key,
tag: instance,
};
}
propsPayload[key] = value;
}
return propsPayload;
}
function arePropsEqual(oldProps: Props, newProps: Props): boolean {
var key;
for (key in newProps) {
if (key === 'children') {
// Skip special case.
continue;
}
if (newProps[key] !== oldProps[key]) {
return false;
}
}
for (key in oldProps) {
if (key === 'children') {
// Skip special case.
continue;
}
if (!(key in newProps)) {
return false;
}
}
return true;
}
const ReactNativeCSFiberRenderer = ReactFiberReconciler({
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: {},
internalInstanceHandle: Object,
): Instance {
return 0;
},
createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: {},
internalInstanceHandle: Object,
): TextInstance {
return 0;
},
finalizeInitialChildren(
parentInstance: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
): boolean {
return false;
},
getRootHostContext(): {} {
return emptyObject;
},
getChildHostContext(): {} {
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit(): void {},
prepareUpdate(
instance: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: {},
): null | Object {
if (arePropsEqual(oldProps, newProps)) {
return null;
}
return processProps(instance, newProps);
},
resetAfterCommit(): void {},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
scheduleDeferredCallback: global.requestIdleCallback,
shouldSetTextContent(type: string, props: Props): boolean {
// TODO: Figure out when we should allow text content.
return false;
},
useSyncScheduling: true,
now(): number {
// TODO: Enable expiration by implementing this method.
return 0;
},
persistence: {
cloneInstance(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
keepChildren: boolean,
): Instance {
return 0;
},
tryToReuseInstance(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
keepChildren: boolean,
): Instance {
return 0;
},
createRootInstance(
rootContainerInstance: Container,
hostContext: {},
): Instance {
return 123;
},
commitRootInstance(rootInstance: Instance): void {},
},
});
const roots = new Map();
const ReactNativeCSFiber: ReactNativeCSType = {
render(element: React$Element<any>, containerTag: any, callback: ?Function) {
let root = roots.get(containerTag);
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = ReactNativeCSFiberRenderer.createContainer(containerTag);
roots.set(containerTag, root);
}
ReactNativeCSFiberRenderer.updateContainer(element, root, null, callback);
return ReactNativeCSFiberRenderer.getPublicRootInstance(root);
},
unmountComponentAtNode(containerTag: number) {
const root = roots.get(containerTag);
if (root) {
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
ReactNativeCSFiberRenderer.updateContainer(null, root, null, () => {
roots.delete(containerTag);
});
}
},
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
flushSync: ReactNativeCSFiberRenderer.flushSync,
};
injectInternals({
findHostInstanceByFiber: ReactNativeCSFiberRenderer.findHostInstance,
// This is an enum because we may add more (e.g. profiler build)
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-native-cs',
});
module.exports = ReactNativeCSFiber;

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule ReactNativeCSTypes
* @flow
*/
'use strict';
/**
* Flat CS renderer bundles are too big for Flow to parse efficiently.
* Provide minimal Flow typing for the high-level RN API and call it a day.
*/
export type ReactNativeCSType = {
render(
element: React$Element<any>,
containerTag: any,
callback: ?Function,
): any,
unmountComponentAtNode(containerTag: number): any,
unstable_batchedUpdates: any, // TODO (bvaughn) Add types
};

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* 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';
var React;
var ReactNativeCS;
describe('ReactNativeCS', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNativeCS = require('ReactNativeCSFiberEntry');
});
it('should be able to create and render a native component', () => {
const RCTView = 'View';
ReactNativeCS.render(<RCTView foo="test" />, 1);
});
});

View File

@@ -69,17 +69,6 @@ function arePropsEqual(oldProps: Props, newProps: Props): boolean {
}
const NativeRTRenderer = ReactFiberReconciler({
appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
RTManager.appendChild(parentInstance, child);
},
appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
RTManager.appendChildToContext(parentInstance, child);
},
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
@@ -87,35 +76,6 @@ const NativeRTRenderer = ReactFiberReconciler({
RTManager.appendChild(parentInstance, child);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
invariant(false, 'Text components are not yet supported.');
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
updateFiberProps(instance, newProps);
RTManager.updateNode(instance, updatePayload);
},
createInstance(
type: string,
props: Props,
@@ -160,22 +120,6 @@ const NativeRTRenderer = ReactFiberReconciler({
return instance;
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
RTManager.prependChild(child, beforeChild);
},
insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
RTManager.prependChild(child, beforeChild);
},
prepareForCommit(): void {
RTManager.beginUpdates();
},
@@ -194,27 +138,10 @@ const NativeRTRenderer = ReactFiberReconciler({
return processProps(instance, newProps);
},
removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
// TODO: recursively uncache, by traversing fibers, this will currently leak
RTManager.deleteChild(child);
},
removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
// TODO: recursively uncache, by traversing fibers, this will currently leak
RTManager.deleteChild(child);
},
resetAfterCommit(): void {
RTManager.completeUpdates();
},
resetTextContent(instance: Instance): void {
// Noop
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
@@ -232,6 +159,87 @@ const NativeRTRenderer = ReactFiberReconciler({
// TODO: Enable expiration by implementing this method.
return 0;
},
mutation: {
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
RTManager.appendChild(parentInstance, child);
},
appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
RTManager.appendChildToContext(parentInstance, child);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
invariant(false, 'Text components are not yet supported.');
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
updateFiberProps(instance, newProps);
RTManager.updateNode(instance, updatePayload);
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
RTManager.prependChild(child, beforeChild);
},
insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
RTManager.prependChild(child, beforeChild);
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
// TODO: recursively uncache, by traversing fibers, this will currently leak
RTManager.deleteChild(child);
},
removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
// TODO: recursively uncache, by traversing fibers, this will currently leak
RTManager.deleteChild(child);
},
resetTextContent(instance: Instance): void {
// Noop
},
},
});
module.exports = NativeRTRenderer;

View File

@@ -51,48 +51,6 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
}
const NativeRenderer = ReactFiberReconciler({
appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
const children = parentInstance._children;
const index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
},
appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.setChildren(
parentInstance, // containerTag
[childTag], // reactTags
);
},
appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
@@ -100,57 +58,6 @@ const NativeRenderer = ReactFiberReconciler({
parentInstance._children.push(child);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayloadTODO: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload, // props
);
}
},
createInstance(
type: string,
props: Props,
@@ -251,60 +158,6 @@ const NativeRenderer = ReactFiberReconciler({
return instance;
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const children = (parentInstance: any)._children;
const index = children.indexOf(child);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
},
insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
},
prepareForCommit(): void {
// Noop
},
@@ -320,46 +173,10 @@ const NativeRenderer = ReactFiberReconciler({
return emptyObject;
},
removeChild(parentInstance: Instance, child: Instance | TextInstance): void {
recursivelyUncacheFiberNode(child);
const children = parentInstance._children;
const index = children.indexOf(child);
children.splice(index, 1);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
},
removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
},
resetAfterCommit(): void {
// Noop
},
resetTextContent(instance: Instance): void {
// Noop
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
@@ -382,6 +199,197 @@ const NativeRenderer = ReactFiberReconciler({
// TODO: Enable expiration by implementing this method.
return 0;
},
mutation: {
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
const children = parentInstance._children;
const index = children.indexOf(child);
if (index >= 0) {
children.splice(index, 1);
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[index], // moveFromIndices
[children.length - 1], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
children.push(child);
UIManager.manageChildren(
parentInstance._nativeTag, // containerTag
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[children.length - 1], // addAtIndices
[], // removeAtIndices
);
}
},
appendChildToContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.setChildren(
parentInstance, // containerTag
[childTag], // reactTags
);
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
UIManager.updateView(
textInstance, // reactTag
'RCTRawText', // viewName
{text: newText}, // props
);
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayloadTODO: Object,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
const viewConfig = instance.viewConfig;
updateFiberProps(instance._nativeTag, newProps);
const updatePayload = ReactNativeAttributePayload.diff(
oldProps,
newProps,
viewConfig.validAttributes,
);
// Avoid the overhead of bridge calls if there's no update.
// This is an expensive no-op for Android, and causes an unnecessary
// view invalidation for certain components (eg RCTTextInput) on iOS.
if (updatePayload != null) {
UIManager.updateView(
instance._nativeTag, // reactTag
viewConfig.uiViewClassName, // viewName
updatePayload, // props
);
}
},
insertBefore(
parentInstance: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
const children = (parentInstance: any)._children;
const index = children.indexOf(child);
// Move existing child or add new child?
if (index >= 0) {
children.splice(index, 1);
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[index], // moveFromIndices
[beforeChildIndex], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[], // removeAtIndices
);
} else {
const beforeChildIndex = children.indexOf(beforeChild);
children.splice(beforeChildIndex, 0, child);
const childTag = typeof child === 'number' ? child : child._nativeTag;
UIManager.manageChildren(
(parentInstance: any)._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[childTag], // addChildReactTags
[beforeChildIndex], // addAtIndices
[], // removeAtIndices
);
}
},
insertInContainerBefore(
parentInstance: Container,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance,
): void {
// TODO (bvaughn): Remove this check when...
// We create a wrapper object for the container in ReactNative render()
// Or we refactor to remove wrapper objects entirely.
// For more info on pros/cons see PR #8560 description.
invariant(
typeof parentInstance !== 'number',
'Container does not support insertBefore operation',
);
},
removeChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
const children = parentInstance._children;
const index = children.indexOf(child);
children.splice(index, 1);
UIManager.manageChildren(
parentInstance._nativeTag, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[index], // removeAtIndices
);
},
removeChildFromContainer(
parentInstance: Container,
child: Instance | TextInstance,
): void {
recursivelyUncacheFiberNode(child);
UIManager.manageChildren(
parentInstance, // containerID
[], // moveFromIndices
[], // moveToIndices
[], // addChildReactTags
[], // addAtIndices
[0], // removeAtIndices
);
},
resetTextContent(instance: Instance): void {
// Noop
},
},
});
module.exports = NativeRenderer;

View File

@@ -137,28 +137,12 @@ var NoopRenderer = ReactFiberReconciler({
return UPDATE_SIGNAL;
},
commitMount(instance: Instance, type: string, newProps: Props): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
): void {
instance.prop = newProps.prop;
},
shouldSetTextContent(type: string, props: Props): boolean {
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
},
resetTextContent(instance: Instance): void {},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return !!props.hidden;
},
@@ -175,21 +159,6 @@ var NoopRenderer = ReactFiberReconciler({
return inst;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
scheduleDeferredCallback(callback) {
if (scheduledCallback) {
throw new Error(
@@ -207,6 +176,39 @@ var NoopRenderer = ReactFiberReconciler({
now(): number {
return elapsedTimeInMs;
},
mutation: {
commitMount(instance: Instance, type: string, newProps: Props): void {
// Noop
},
commitUpdate(
instance: Instance,
updatePayload: Object,
type: string,
oldProps: Props,
newProps: Props,
): void {
instance.prop = newProps.prop;
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
resetTextContent(instance: Instance): void {},
},
});
var rootContainers = new Map();

View File

@@ -47,19 +47,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
captureError: (failedFiber: Fiber, error: mixed) => Fiber | null,
) {
const {
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
appendChildToContainer,
insertBefore,
insertInContainerBefore,
removeChild,
removeChildFromContainer,
getPublicInstance,
} = config;
const {getPublicInstance} = config;
if (__DEV__) {
var callComponentWillUnmountWithTimerInDev = function(current, instance) {
@@ -115,6 +103,213 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
function commitLifeCycles(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidMount');
}
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
if (__DEV__) {
stopPhaseTimer();
}
} else {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidUpdate');
}
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
if (__DEV__) {
stopPhaseTimer();
}
}
}
if (
finishedWork.effectTag & Callback &&
finishedWork.updateQueue !== null
) {
commitCallbacks(finishedWork, finishedWork.updateQueue, instance);
}
return;
}
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
const instance = finishedWork.child && finishedWork.child.stateNode;
commitCallbacks(finishedWork, updateQueue, instance);
}
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
switch (finishedWork.tag) {
case HostComponent:
ref(getPublicInstance(instance));
break;
default:
ref(instance);
}
}
}
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
currentRef(null);
}
}
// User-originating errors (lifecycles and refs) should not interrupt
// deletion, so don't let them throw. Host-originating errors should
// interrupt deletion, so it's okay
function commitUnmount(current: Fiber): void {
if (typeof onCommitUnmount === 'function') {
onCommitUnmount(current);
}
switch (current.tag) {
case ClassComponent: {
safelyDetachRef(current);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
safelyDetachRef(current);
return;
}
case CoroutineComponent: {
commitNestedUnmounts(current.stateNode);
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
unmountHostComponents(current);
return;
}
}
}
function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
while (true) {
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (node.child !== null && node.tag !== HostPortal) {
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function detachFiber(current: Fiber) {
// Cut off the return pointers to disconnect it from the tree. Ideally, we
// should clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child. This child
// itself will be GC:ed when the parent updates the next time.
current.return = null;
current.child = null;
if (current.alternate) {
current.alternate.child = null;
current.alternate.return = null;
}
}
if (!config.mutation) {
return {
commitResetTextContent(finishedWork: Fiber) {},
commitPlacement(finishedWork: Fiber) {},
commitDeletion(current: Fiber) {
// Detach refs and call componentWillUnmount() on the whole subtree.
commitNestedUnmounts(current);
detachFiber(current);
},
commitWork(current: Fiber | null, finishedWork: Fiber) {},
commitLifeCycles,
commitAttachRef,
commitDetachRef,
};
}
const {
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
appendChildToContainer,
insertBefore,
insertInContainerBefore,
removeChild,
removeChildFromContainer,
} = config.mutation;
function getHostParentFiber(fiber: Fiber): Fiber {
let parent = fiber.return;
while (parent !== null) {
@@ -254,36 +449,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
function commitNestedUnmounts(root: Fiber): void {
// While we're inside a removed host node we don't want to call
// removeChild on the inner nodes because they're removed by the top
// call anyway. We also want to call componentWillUnmount on all
// composites before this host node is removed from the tree. Therefore
// we do an inner loop while we're still inside the host node.
let node: Fiber = root;
while (true) {
commitUnmount(node);
// Visit children because they may contain more composite or host nodes.
// Skip portals because commitUnmount() currently visits them recursively.
if (node.child !== null && node.tag !== HostPortal) {
node.child.return = node;
node = node.child;
continue;
}
if (node === root) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === root) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function unmountHostComponents(current): void {
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
@@ -375,53 +540,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
// Recursively delete all host nodes from the parent.
// Detach refs and call componentWillUnmount() on the whole subtree.
unmountHostComponents(current);
// Cut off the return pointers to disconnect it from the tree. Ideally, we
// should clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child. This child
// itself will be GC:ed when the parent updates the next time.
current.return = null;
current.child = null;
if (current.alternate) {
current.alternate.child = null;
current.alternate.return = null;
}
}
// User-originating errors (lifecycles and refs) should not interrupt
// deletion, so don't let them throw. Host-originating errors should
// interrupt deletion, so it's okay
function commitUnmount(current: Fiber): void {
if (typeof onCommitUnmount === 'function') {
onCommitUnmount(current);
}
switch (current.tag) {
case ClassComponent: {
safelyDetachRef(current);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(current, instance);
}
return;
}
case HostComponent: {
safelyDetachRef(current);
return;
}
case CoroutineComponent: {
commitNestedUnmounts(current.stateNode);
return;
}
case HostPortal: {
// TODO: this is recursive.
// We are also not using this parent because
// the portal will get pushed immediately.
unmountHostComponents(current);
return;
}
}
detachFiber(current);
}
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
@@ -488,106 +607,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}
}
function commitLifeCycles(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidMount');
}
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidMount();
if (__DEV__) {
stopPhaseTimer();
}
} else {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
if (__DEV__) {
startPhaseTimer(finishedWork, 'componentDidUpdate');
}
instance.props = finishedWork.memoizedProps;
instance.state = finishedWork.memoizedState;
instance.componentDidUpdate(prevProps, prevState);
if (__DEV__) {
stopPhaseTimer();
}
}
}
if (
finishedWork.effectTag & Callback &&
finishedWork.updateQueue !== null
) {
commitCallbacks(finishedWork, finishedWork.updateQueue, instance);
}
return;
}
case HostRoot: {
const updateQueue = finishedWork.updateQueue;
if (updateQueue !== null) {
const instance = finishedWork.child && finishedWork.child.stateNode;
commitCallbacks(finishedWork, updateQueue, instance);
}
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
function commitAttachRef(finishedWork: Fiber) {
const ref = finishedWork.ref;
if (ref !== null) {
const instance = finishedWork.stateNode;
switch (finishedWork.tag) {
case HostComponent:
ref(getPublicInstance(instance));
break;
default:
ref(instance);
}
}
}
function commitDetachRef(current: Fiber) {
const currentRef = current.ref;
if (currentRef !== null) {
currentRef(null);
}
function commitResetTextContent(current: Fiber) {
resetTextContent(current.stateNode);
}
return {
commitResetTextContent,
commitPlacement,
commitDeletion,
commitWork,

View File

@@ -36,42 +36,10 @@ export type HydrationContext<C, CX> = {
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
config: HostConfig<T, P, I, TI, PI, C, CX, PL>,
): HydrationContext<C, CX> {
const {
shouldSetTextContent,
canHydrateInstance,
canHydrateTextInstance,
getNextHydratableSibling,
getFirstHydratableChild,
hydrateInstance,
hydrateTextInstance,
didNotMatchHydratedContainerTextInstance,
didNotMatchHydratedTextInstance,
didNotHydrateContainerInstance,
didNotHydrateInstance,
// TODO: These are currently unused, see below.
// didNotFindHydratableContainerInstance,
// didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} = config;
const {shouldSetTextContent, hydration} = config;
// If this doesn't have hydration mode.
if (
!(canHydrateInstance &&
canHydrateTextInstance &&
getNextHydratableSibling &&
getFirstHydratableChild &&
hydrateInstance &&
hydrateTextInstance &&
didNotMatchHydratedContainerTextInstance &&
didNotMatchHydratedTextInstance &&
didNotHydrateContainerInstance &&
didNotHydrateInstance &&
// didNotFindHydratableContainerInstance &&
// didNotFindHydratableContainerTextInstance &&
didNotFindHydratableInstance &&
didNotFindHydratableTextInstance)
) {
if (!hydration) {
return {
enterHydrationState() {
return false;
@@ -98,6 +66,24 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
};
}
const {
canHydrateInstance,
canHydrateTextInstance,
getNextHydratableSibling,
getFirstHydratableChild,
hydrateInstance,
hydrateTextInstance,
didNotMatchHydratedContainerTextInstance,
didNotMatchHydratedTextInstance,
didNotHydrateContainerInstance,
didNotHydrateInstance,
// TODO: These are currently unused, see below.
// didNotFindHydratableContainerInstance,
// didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} = hydration;
// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
let hydrationParentFiber: null | Fiber = null;

View File

@@ -77,6 +77,35 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
rootContainerInstance: C,
hostContext: CX,
): null | PL,
shouldSetTextContent(type: T, props: P): boolean,
shouldDeprioritizeSubtree(type: T, props: P): boolean,
createTextInstance(
text: string,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): TI,
scheduleDeferredCallback(
callback: (deadline: Deadline) => void,
): number | void,
prepareForCommit(): void,
resetAfterCommit(): void,
now(): number,
useSyncScheduling?: boolean,
+hydration?: HydrationHostConfig<T, P, I, TI, C, CX, PL>,
+mutation?: MutableUpdatesHostConfig<T, P, I, TI, C, PL>,
+persistence?: PersistentUpdatesHostConfig<T, P, I, C, CX, PL>,
};
type MutableUpdatesHostConfig<T, P, I, TI, C, PL> = {
commitUpdate(
instance: I,
updatePayload: PL,
@@ -91,19 +120,8 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
newProps: P,
internalInstanceHandle: OpaqueHandle,
): void,
shouldSetTextContent(type: T, props: P): boolean,
resetTextContent(instance: I): void,
shouldDeprioritizeSubtree(type: T, props: P): boolean,
createTextInstance(
text: string,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
): TI,
commitTextUpdate(textInstance: TI, oldText: string, newText: string): void,
resetTextContent(instance: I): void,
appendChild(parentInstance: I, child: I | TI): void,
appendChildToContainer(container: C, child: I | TI): void,
insertBefore(parentInstance: I, child: I | TI, beforeChild: I | TI): void,
@@ -114,80 +132,92 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
): void,
removeChild(parentInstance: I, child: I | TI): void,
removeChildFromContainer(container: C, child: I | TI): void,
};
scheduleDeferredCallback(
callback: (deadline: Deadline) => void,
): number | void,
type PersistentUpdatesHostConfig<T, P, I, C, CX, PL> = {
cloneInstance(
instance: I,
updatePayload: PL,
type: T,
oldProps: P,
newProps: P,
internalInstanceHandle: OpaqueHandle,
keepChildren: boolean,
): I,
tryToReuseInstance(
instance: I,
updatePayload: PL,
type: T,
oldProps: P,
newProps: P,
internalInstanceHandle: OpaqueHandle,
keepChildren: boolean,
): I,
prepareForCommit(): void,
resetAfterCommit(): void,
now(): number,
createRootInstance(rootContainerInstance: C, hostContext: CX): I,
commitRootInstance(rootInstance: I): void,
};
type HydrationHostConfig<T, P, I, TI, C, CX, PL> = {
// Optional hydration
canHydrateInstance?: (instance: I | TI, type: T, props: P) => boolean,
canHydrateTextInstance?: (instance: I | TI, text: string) => boolean,
getNextHydratableSibling?: (instance: I | TI) => null | I | TI,
getFirstHydratableChild?: (parentInstance: I | C) => null | I | TI,
hydrateInstance?: (
canHydrateInstance(instance: I | TI, type: T, props: P): boolean,
canHydrateTextInstance(instance: I | TI, text: string): boolean,
getNextHydratableSibling(instance: I | TI): null | I | TI,
getFirstHydratableChild(parentInstance: I | C): null | I | TI,
hydrateInstance(
instance: I,
type: T,
props: P,
rootContainerInstance: C,
hostContext: CX,
internalInstanceHandle: OpaqueHandle,
) => null | PL,
hydrateTextInstance?: (
): null | PL,
hydrateTextInstance(
textInstance: TI,
text: string,
internalInstanceHandle: OpaqueHandle,
) => boolean,
didNotMatchHydratedContainerTextInstance?: (
): boolean,
didNotMatchHydratedContainerTextInstance(
parentContainer: C,
textInstance: TI,
text: string,
) => void,
didNotMatchHydratedTextInstance?: (
): void,
didNotMatchHydratedTextInstance(
parentType: T,
parentProps: P,
parentInstance: I,
textInstance: TI,
text: string,
) => void,
didNotHydrateContainerInstance?: (
parentContainer: C,
instance: I | TI,
) => void,
didNotHydrateInstance?: (
): void,
didNotHydrateContainerInstance(parentContainer: C, instance: I | TI): void,
didNotHydrateInstance(
parentType: T,
parentProps: P,
parentInstance: I,
instance: I | TI,
) => void,
didNotFindHydratableContainerInstance?: (
): void,
didNotFindHydratableContainerInstance(
parentContainer: C,
type: T,
props: P,
) => void,
didNotFindHydratableContainerTextInstance?: (
): void,
didNotFindHydratableContainerTextInstance(
parentContainer: C,
text: string,
) => void,
didNotFindHydratableInstance?: (
): void,
didNotFindHydratableInstance(
parentType: T,
parentProps: P,
parentInstance: I,
type: T,
props: P,
) => void,
didNotFindHydratableTextInstance?: (
): void,
didNotFindHydratableTextInstance(
parentType: T,
parentProps: P,
parentInstance: I,
text: string,
) => void,
useSyncScheduling?: boolean,
): void,
};
export type Reconciler<C, I, TI> = {

View File

@@ -174,6 +174,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
hydrationContext,
);
const {
commitResetTextContent,
commitPlacement,
commitDeletion,
commitWork,
@@ -332,7 +333,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
config.resetTextContent(nextEffect.stateNode);
commitResetTextContent(nextEffect);
}
if (effectTag & Ref) {

View File

@@ -43,13 +43,15 @@ describe('ReactFiberHostContext', () => {
appendInitialChild: function() {
return null;
},
appendChildToContainer: function() {
return null;
},
now: function() {
return 0;
},
useSyncScheduling: true,
mutation: {
appendChildToContainer: function() {
return null;
},
},
});
const container = Renderer.createContainer(/* root: */ null);

View File

@@ -180,35 +180,10 @@ var TestRenderer = ReactFiberReconciler({
return UPDATE_SIGNAL;
},
commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
},
shouldSetTextContent(type: string, props: Props): boolean {
return false;
},
resetTextContent(testElement: Instance): void {
// noop
},
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
return false;
},
@@ -225,21 +200,6 @@ var TestRenderer = ReactFiberReconciler({
};
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
scheduleDeferredCallback(fn: Function): void {
setTimeout(fn, 0, {timeRemaining: Infinity});
},
@@ -252,6 +212,47 @@ var TestRenderer = ReactFiberReconciler({
// Test renderer does not use expiration
return 0;
},
mutation: {
commitUpdate(
instance: Instance,
updatePayload: {},
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
): void {
instance.type = type;
instance.props = newProps;
},
commitMount(
instance: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
): void {
// noop
},
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string,
): void {
textInstance.text = newText;
},
resetTextContent(testElement: Instance): void {
// noop
},
appendChild: appendChild,
appendChildToContainer: appendChild,
insertBefore: insertBefore,
insertInContainerBefore: insertBefore,
removeChild: removeChild,
removeChildFromContainer: removeChild,
},
});
var defaultTestOptions = {