diff --git a/fixtures/reconciler/index.js b/fixtures/reconciler/index.js index b178006992..8c99bb7541 100644 --- a/fixtures/reconciler/index.js +++ b/fixtures/reconciler/index.js @@ -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(); diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index eb9cbe346d..d2e325c20f 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -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)); } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 6ba9f3dd4a..60dc4994d7 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -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, diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index 33ce443ed0..23d75f9a12 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -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, }; diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 1e345fb8c7..b550705909 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -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 } } } \ No newline at end of file diff --git a/src/renderers/art/ReactARTFiberEntry.js b/src/renderers/art/ReactARTFiberEntry.js index ca97c71e8d..7189b0b871 100644 --- a/src/renderers/art/ReactARTFiberEntry.js +++ b/src/renderers/art/ReactARTFiberEntry.js @@ -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 {'foo'}{'bar'}) @@ -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 */ diff --git a/src/renderers/dom/fiber/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js index 0bd998e288..1bdb813b1e 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js +++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js @@ -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, - 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, + 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 { - 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 { + 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, diff --git a/src/renderers/native-cs/ReactNativeCSFiberEntry.js b/src/renderers/native-cs/ReactNativeCSFiberEntry.js new file mode 100644 index 0000000000..72ceb060c7 --- /dev/null +++ b/src/renderers/native-cs/ReactNativeCSFiberEntry.js @@ -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, 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; diff --git a/src/renderers/native-cs/ReactNativeCSTypes.js b/src/renderers/native-cs/ReactNativeCSTypes.js new file mode 100644 index 0000000000..85c5025dbd --- /dev/null +++ b/src/renderers/native-cs/ReactNativeCSTypes.js @@ -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, + containerTag: any, + callback: ?Function, + ): any, + unmountComponentAtNode(containerTag: number): any, + unstable_batchedUpdates: any, // TODO (bvaughn) Add types +}; diff --git a/src/renderers/native-cs/__tests__/ReactNativeCS-test.js b/src/renderers/native-cs/__tests__/ReactNativeCS-test.js new file mode 100644 index 0000000000..f04074529b --- /dev/null +++ b/src/renderers/native-cs/__tests__/ReactNativeCS-test.js @@ -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(, 1); + }); +}); diff --git a/src/renderers/native-rt/ReactNativeRTFiberRenderer.js b/src/renderers/native-rt/ReactNativeRTFiberRenderer.js index 443c84b1fe..92d42756a0 100644 --- a/src/renderers/native-rt/ReactNativeRTFiberRenderer.js +++ b/src/renderers/native-rt/ReactNativeRTFiberRenderer.js @@ -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; diff --git a/src/renderers/native/ReactNativeFiberRenderer.js b/src/renderers/native/ReactNativeFiberRenderer.js index 859f422f4c..8c24b30da7 100644 --- a/src/renderers/native/ReactNativeFiberRenderer.js +++ b/src/renderers/native/ReactNativeFiberRenderer.js @@ -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; diff --git a/src/renderers/noop/ReactNoopEntry.js b/src/renderers/noop/ReactNoopEntry.js index 6a5ed0b11e..bf964eb84e 100644 --- a/src/renderers/noop/ReactNoopEntry.js +++ b/src/renderers/noop/ReactNoopEntry.js @@ -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(); diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index 98a8f3def9..32e7fd9bc7 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -47,19 +47,7 @@ module.exports = function( config: HostConfig, 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( } } + 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( } } - 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( // 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( } } - 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, diff --git a/src/renderers/shared/fiber/ReactFiberHydrationContext.js b/src/renderers/shared/fiber/ReactFiberHydrationContext.js index 879bee732e..c1475017dd 100644 --- a/src/renderers/shared/fiber/ReactFiberHydrationContext.js +++ b/src/renderers/shared/fiber/ReactFiberHydrationContext.js @@ -36,42 +36,10 @@ export type HydrationContext = { module.exports = function( config: HostConfig, ): HydrationContext { - 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( }; } + 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; diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 8df44b6dc2..697f41ca35 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -77,6 +77,35 @@ export type HostConfig = { 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, + + +mutation?: MutableUpdatesHostConfig, + +persistence?: PersistentUpdatesHostConfig, +}; + +type MutableUpdatesHostConfig = { commitUpdate( instance: I, updatePayload: PL, @@ -91,19 +120,8 @@ export type HostConfig = { 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 = { ): void, removeChild(parentInstance: I, child: I | TI): void, removeChildFromContainer(container: C, child: I | TI): void, +}; - scheduleDeferredCallback( - callback: (deadline: Deadline) => void, - ): number | void, +type PersistentUpdatesHostConfig = { + 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 = { // 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 = { diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index bf026e60f4..13bcea3b5b 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -174,6 +174,7 @@ module.exports = function( hydrationContext, ); const { + commitResetTextContent, commitPlacement, commitDeletion, commitWork, @@ -332,7 +333,7 @@ module.exports = function( const effectTag = nextEffect.effectTag; if (effectTag & ContentReset) { - config.resetTextContent(nextEffect.stateNode); + commitResetTextContent(nextEffect); } if (effectTag & Ref) { diff --git a/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js b/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js index 6041663271..a2bb03f609 100644 --- a/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactFiberHostContext-test.js @@ -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); diff --git a/src/renderers/testing/ReactTestRendererFiberEntry.js b/src/renderers/testing/ReactTestRendererFiberEntry.js index 2a6d9e1ea5..1b0253f512 100644 --- a/src/renderers/testing/ReactTestRendererFiberEntry.js +++ b/src/renderers/testing/ReactTestRendererFiberEntry.js @@ -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 = {