diff --git a/flow/react-native-host-hooks.js b/flow/react-native-host-hooks.js index b52951fcd3a3d..0f96747133227 100644 --- a/flow/react-native-host-hooks.js +++ b/flow/react-native-host-hooks.js @@ -29,16 +29,35 @@ declare module 'TextInputState' { declare module 'UIManager' { declare var customBubblingEventTypes : Object; declare var customDirectEventTypes : Object; - declare function createView() : void; - declare function manageChildren() : void; + declare function createView( + reactTag : number, + viewName : string, + rootTag : number, + props : ?Object, + ) : void; + declare function manageChildren( + containerTag : number, + moveFromIndices : Array, + moveToIndices : Array, + addChildReactTags : Array, + addAtIndices : Array, + removeAtIndices : Array + ) : void; declare function measure() : void; declare function measureInWindow() : void; declare function measureLayout() : void; declare function removeRootView() : void; declare function removeSubviewsFromContainerWithID() : void; declare function replaceExistingNonRootView() : void; - declare function setChildren() : void; - declare function updateView() : void; + declare function setChildren( + containerTag : number, + reactTags : Array, + ) : void; + declare function updateView( + reactTag : number, + viewName : string, + props : ?Object, + ) : void; } declare module 'View' { declare var exports : typeof ReactComponent; diff --git a/src/renderers/native/NativeMethodsMixin.js b/src/renderers/native/NativeMethodsMixin.js index 5bc75fc332994..e212f7ed30f63 100644 --- a/src/renderers/native/NativeMethodsMixin.js +++ b/src/renderers/native/NativeMethodsMixin.js @@ -150,7 +150,7 @@ var NativeMethodsMixin = { ); UIManager.updateView( - findNodeHandle(this), + (findNodeHandle(this) : any), this.viewConfig.uiViewClassName, updatePayload ); diff --git a/src/renderers/native/ReactNative.js b/src/renderers/native/ReactNative.js index 6112e9045b2b3..27c56d54ba9d5 100644 --- a/src/renderers/native/ReactNative.js +++ b/src/renderers/native/ReactNative.js @@ -11,67 +11,5 @@ */ 'use strict'; -// Require ReactNativeDefaultInjection first for its side effects of setting up -// the JS environment -var ReactNativeComponentTree = require('ReactNativeComponentTree'); -var ReactNativeInjection = require('ReactNativeInjection'); -var ReactNativeStackInjection = require('ReactNativeStackInjection'); - -var ReactNativeMount = require('ReactNativeMount'); -var ReactUpdates = require('ReactUpdates'); - -var findNodeHandle = require('findNodeHandle'); - -ReactNativeInjection.inject(); -ReactNativeStackInjection.inject(); - -var render = function( - element: ReactElement, - mountInto: number, - callback?: ?(() => void) -): ?ReactComponent { - return ReactNativeMount.renderComponent(element, mountInto, callback); -}; - -var ReactNative = { - hasReactNativeInitialized: false, - findNodeHandle: findNodeHandle, - render: render, - unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, - - /* eslint-disable camelcase */ - unstable_batchedUpdates: ReactUpdates.batchedUpdates, - /* eslint-enable camelcase */ - - unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, -}; - -// Inject the runtime into a devtools global hook regardless of browser. -// Allows for debugging when the hook is injected on the page. -/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */ -if ( - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { - __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ - ComponentTree: { - getClosestInstanceFromNode: function(node) { - return ReactNativeComponentTree.getClosestInstanceFromNode(node); - }, - getNodeFromInstance: function(inst) { - // inst is an internal instance (but could be a composite) - while (inst._renderedComponent) { - inst = inst._renderedComponent; - } - if (inst) { - return ReactNativeComponentTree.getNodeFromInstance(inst); - } else { - return null; - } - }, - }, - Mount: ReactNativeMount, - Reconciler: require('ReactReconciler'), - }); -} - -module.exports = ReactNative; +// TODO (bvaughn) Enable Fiber experiement via ReactNativeFeatureFlags +module.exports = require('ReactNativeStack'); diff --git a/src/renderers/native/ReactNativeComponentTree.js b/src/renderers/native/ReactNativeComponentTree.js index 7b16b8512a172..e95932a03611b 100644 --- a/src/renderers/native/ReactNativeComponentTree.js +++ b/src/renderers/native/ReactNativeComponentTree.js @@ -39,6 +39,10 @@ function precacheNode(inst, tag) { instanceCache[tag] = nativeInst; } +function precacheFiberNode(hostInst, tag) { + instanceCache[tag] = hostInst; +} + function uncacheNode(inst) { var tag = inst._rootNodeID; if (tag) { @@ -46,21 +50,29 @@ function uncacheNode(inst) { } } +function uncacheFiberNode(tag) { + delete instanceCache[tag]; +} + function getInstanceFromTag(tag) { return instanceCache[tag] || null; } function getTagFromInstance(inst) { - invariant(inst._rootNodeID, 'All native instances should have a tag.'); - return inst._rootNodeID; + // TODO (bvaughn) Clean up once Stack is deprecated + var tag = inst._rootNodeID || inst.stateNode._nativeTag; + invariant(tag, 'All native instances should have a tag.'); + return tag; } var ReactNativeComponentTree = { getClosestInstanceFromNode: getInstanceFromTag, getInstanceFromNode: getInstanceFromTag, getNodeFromInstance: getTagFromInstance, - precacheNode: precacheNode, - uncacheNode: uncacheNode, + precacheFiberNode, + precacheNode, + uncacheFiberNode, + uncacheNode, }; module.exports = ReactNativeComponentTree; diff --git a/src/renderers/native/ReactNativeFeatureFlags.js b/src/renderers/native/ReactNativeFeatureFlags.js new file mode 100644 index 0000000000000..1686eec51aed2 --- /dev/null +++ b/src/renderers/native/ReactNativeFeatureFlags.js @@ -0,0 +1,18 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeFeatureFlags + */ + +'use strict'; + +var ReactNativeFeatureFlags = { + useFiber: false, +}; + +module.exports = ReactNativeFeatureFlags; diff --git a/src/renderers/native/ReactNativeFiber.js b/src/renderers/native/ReactNativeFiber.js new file mode 100644 index 0000000000000..d11438a114795 --- /dev/null +++ b/src/renderers/native/ReactNativeFiber.js @@ -0,0 +1,395 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeFiber + * @flow + */ + +'use strict'; + +import type { Element } from 'React'; +import type { Fiber } from 'ReactFiber'; +import type { ReactNodeList } from 'ReactTypes'; +import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry'; + +const NativeMethodsMixin = require('NativeMethodsMixin'); +const ReactFiberReconciler = require('ReactFiberReconciler'); +const ReactGenericBatching = require('ReactGenericBatching'); +const ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +const ReactNativeComponentTree = require('ReactNativeComponentTree'); +const ReactNativeInjection = require('ReactNativeInjection'); +const ReactNativeTagHandles = require('ReactNativeTagHandles'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); +const ReactPortal = require('ReactPortal'); +const UIManager = require('UIManager'); + +const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); +const findNodeHandle = require('findNodeHandle'); +const invariant = require('invariant'); + +const { precacheFiberNode, uncacheFiberNode } = ReactNativeComponentTree; + +ReactNativeInjection.inject(); + +type Container = number; +type Instance = { + _children: Array, + _nativeTag: number, + viewConfig: ReactNativeBaseComponentViewConfig, +}; +type Props = Object; +type TextInstance = number; + +function NativeHostComponent(tag, viewConfig) { + this._nativeTag = tag; + this._children = []; + this.viewConfig = viewConfig; +} +Object.assign(NativeHostComponent.prototype, NativeMethodsMixin); + +function recursivelyUncacheFiberNode(node : Instance | TextInstance) { + if (typeof node === 'number') { // Leaf node (eg text) + uncacheFiberNode(node); + } else { + uncacheFiberNode((node : any)._nativeTag); + + (node : any)._children.forEach(recursivelyUncacheFiberNode); + } +} + +const NativeRenderer = ReactFiberReconciler({ + appendChild( + parentInstance : Instance | Container, + child : Instance | TextInstance + ) : void { + if (typeof parentInstance === 'number') { + // Root container + UIManager.setChildren( + parentInstance, // containerTag + [(child : any)._nativeTag] // reactTags + ); + } else { + const children = parentInstance._children; + + children.push(child); + + UIManager.manageChildren( + parentInstance._nativeTag, // containerTag + [], // moveFromIndices + [], // moveToIndices + [(child : any)._nativeTag], // addChildReactTags + [children.length - 1], // addAtIndices + [], // removeAtIndices + ); + } + }, + + appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void { + if (typeof child === 'number') { + parentInstance._children.push(child); + } else { + parentInstance._children.push(child); + } + }, + + commitTextUpdate( + textInstance : TextInstance, + oldText : string, + newText : string + ) : void { + UIManager.updateView( + textInstance, // reactTag + 'RCTRawText', // viewName + {text: newText}, // props + ); + }, + + commitUpdate( + instance : Instance, + type : string, + oldProps : Props, + newProps : Props, + rootContainerInstance : Object, + internalInstanceHandle : Object + ) : void { + const viewConfig = instance.viewConfig; + + precacheFiberNode(internalInstanceHandle, instance._nativeTag); + + const updatePayload = ReactNativeAttributePayload.diff( + oldProps, + newProps, + viewConfig.validAttributes + ); + + UIManager.updateView( + (instance : any)._nativeTag, // reactTag + viewConfig.uiViewClassName, // viewName + updatePayload, // props + ); + }, + + createInstance( + type : string, + props : Props, + rootContainerInstance : Container, + hostContext : string | null, + internalInstanceHandle : Object + ) : Instance { + const tag = ReactNativeTagHandles.allocateTag(); + const viewConfig = ReactNativeViewConfigRegistry.get(type); + + if (__DEV__) { + for (let key in viewConfig.validAttributes) { + if (props.hasOwnProperty(key)) { + deepFreezeAndThrowOnMutationInDev(props[key]); + } + } + } + + const updatePayload = ReactNativeAttributePayload.create( + props, + viewConfig.validAttributes + ); + + UIManager.createView( + tag, // reactTag + viewConfig.uiViewClassName, // viewName + rootContainerInstance, // rootTag + updatePayload, // props + ); + + const component = new NativeHostComponent(tag, viewConfig); + + precacheFiberNode(internalInstanceHandle, tag); + + return component; + }, + + createTextInstance( + text : string, + rootContainerInstance : Container, + internalInstanceHandle : Object, + ) : TextInstance { + const tag = ReactNativeTagHandles.allocateTag(); + + UIManager.createView( + tag, // reactTag + 'RCTRawText', // viewName + rootContainerInstance, // rootTag + {text: text} // props + ); + + precacheFiberNode(internalInstanceHandle, tag); + + return tag; + }, + + finalizeInitialChildren( + parentInstance : Instance, + type : string, + props : Props, + rootContainerInstance : Container, + ) : void { + // Map from child objects to native tags. + // Either way we need to pass a copy of the Array to prevent it from being frozen. + const nativeTags = parentInstance._children.map( + (child) => typeof child === 'number' + ? child // Leaf node (eg text) + : child._nativeTag + ); + + UIManager.setChildren( + parentInstance._nativeTag, // containerTag + nativeTags // reactTags + ); + }, + + getChildHostContext( + parentHostContext : string | null, + type : string + ) { + return parentHostContext; + }, + + insertBefore( + parentInstance : Instance | 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', + ); + + const children = (parentInstance : any)._children; + + const beforeChildIndex = children.indexOf(beforeChild); + const index = children.indexOf(child); + + // Move existing child or add new child? + if (index >= 0) { + children.splice(index, 1); + children.splice(beforeChildIndex, 0, child); + + UIManager.manageChildren( + (parentInstance : any)._nativeTag, // containerID + [index], // moveFromIndices + [beforeChildIndex], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [], // removeAtIndices + ); + } else { + children.splice(beforeChildIndex, 0, child); + + UIManager.manageChildren( + (parentInstance : any)._nativeTag, // containerID + [], // moveFromIndices + [], // moveToIndices + [(child : any)._nativeTag], // addChildReactTags + [beforeChildIndex], // addAtIndices + [], // removeAtIndices + ); + } + }, + + prepareForCommit() : void { + // Noop + }, + + prepareUpdate( + instance : Instance, + type : string, + oldProps : Props, + newProps : Props + ) : boolean { + return true; + }, + + removeChild( + parentInstance : Instance | Container, + child : Instance | TextInstance + ) : void { + recursivelyUncacheFiberNode(child); + + if (typeof parentInstance === 'number') { + UIManager.manageChildren( + parentInstance, // containerID + [], // moveFromIndices + [], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [0], // removeAtIndices + ); + } else { + const children = parentInstance._children; + const index = children.indexOf(child); + + children.splice(index, 1); + + UIManager.manageChildren( + parentInstance._nativeTag, // containerID + [], // moveFromIndices + [], // moveToIndices + [], // addChildReactTags + [], // addAtIndices + [index], // removeAtIndices + ); + } + }, + + resetAfterCommit() : void { + // Noop + }, + + resetTextContent(instance : Instance) : void { + // Noop + }, + + scheduleAnimationCallback: global.requestAnimationFrame, + + scheduleDeferredCallback: global.requestIdleCallback, + + shouldSetTextContent(props : Props) : boolean { + // TODO (bvaughn) Revisit this decision. + // Always returning false simplifies the createInstance() implementation, + // But creates an additional child Fiber for raw text children. + // No additional native views are created though. + // It's not clear to me which is better so I'm deferring for now. + // More context @ github.com/facebook/react/pull/8560#discussion_r92111303 + return false; + }, + + useSyncScheduling: true, +}); + +ReactGenericBatching.injection.injectFiberBatchedUpdates( + NativeRenderer.batchedUpdates +); + +const roots = new Map(); + +findNodeHandle.injection.injectFindNode( + (fiber: Fiber) => { + const instance: any = NativeRenderer.findHostInstance(fiber); + return instance ? instance._nativeTag : null; + } +); +findNodeHandle.injection.injectFindRootNodeID( + (instance) => instance._nativeTag +); + +const ReactNative = { + findNodeHandle, + + render(element : 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 = NativeRenderer.mountContainer(element, containerTag, null, callback); + + roots.set(containerTag, root); + } else { + NativeRenderer.updateContainer(element, root, null, callback); + } + + return NativeRenderer.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? + roots.delete(containerTag); + NativeRenderer.unmountContainer(root); + } + }, + + unmountComponentAtNodeAndRemoveContainer(containerTag: number) { + ReactNative.unmountComponentAtNode(containerTag); + + // Call back into native to remove all of the subviews from this container + UIManager.removeRootView(containerTag); + }, + + unstable_createPortal(children: ReactNodeList, containerTag : number, key : ?string = null) { + return ReactPortal.createPortal(children, containerTag, null, key); + }, + + unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, + +}; + +module.exports = ReactNative; diff --git a/src/renderers/native/ReactNativeGlobalResponderHandler.js b/src/renderers/native/ReactNativeGlobalResponderHandler.js index 84bd45fb6f1f6..4efacfd829023 100644 --- a/src/renderers/native/ReactNativeGlobalResponderHandler.js +++ b/src/renderers/native/ReactNativeGlobalResponderHandler.js @@ -15,8 +15,9 @@ var UIManager = require('UIManager'); var ReactNativeGlobalResponderHandler = { onChange: function(from, to, blockNativeResponder) { if (to !== null) { + // TODO (bvaughn) Clean up once Stack is deprecated UIManager.setJSResponder( - to._rootNodeID, + to._rootNodeID || to.stateNode._nativeTag, blockNativeResponder ); } else { diff --git a/src/renderers/native/ReactNativeStack.js b/src/renderers/native/ReactNativeStack.js new file mode 100644 index 0000000000000..55cf71fa1c6be --- /dev/null +++ b/src/renderers/native/ReactNativeStack.js @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeStack + * @flow + */ +'use strict'; + +var ReactNativeComponentTree = require('ReactNativeComponentTree'); +var ReactNativeInjection = require('ReactNativeInjection'); +var ReactNativeStackInjection = require('ReactNativeStackInjection'); +var ReactNativeMount = require('ReactNativeMount'); +var ReactUpdates = require('ReactUpdates'); + +var findNodeHandle = require('findNodeHandle'); + +ReactNativeInjection.inject(); +ReactNativeStackInjection.inject(); + +var render = function( + element: ReactElement, + mountInto: number, + callback?: ?(() => void) +): ?ReactComponent { + return ReactNativeMount.renderComponent(element, mountInto, callback); +}; + +findNodeHandle.injection.injectFindNode( + (instance) => instance.getHostNode() +); +findNodeHandle.injection.injectFindRootNodeID( + (instance) => instance._rootNodeID +); + +var ReactNative = { + hasReactNativeInitialized: false, + findNodeHandle: findNodeHandle, + render: render, + unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, + + /* eslint-disable camelcase */ + unstable_batchedUpdates: ReactUpdates.batchedUpdates, + /* eslint-enable camelcase */ + + unmountComponentAtNodeAndRemoveContainer: ReactNativeMount.unmountComponentAtNodeAndRemoveContainer, +}; + +// Inject the runtime into a devtools global hook regardless of browser. +// Allows for debugging when the hook is injected on the page. +/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__ */ +if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { + __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ + ComponentTree: { + getClosestInstanceFromNode: function(node) { + return ReactNativeComponentTree.getClosestInstanceFromNode(node); + }, + getNodeFromInstance: function(inst) { + // inst is an internal instance (but could be a composite) + while (inst._renderedComponent) { + inst = inst._renderedComponent; + } + if (inst) { + return ReactNativeComponentTree.getNodeFromInstance(inst); + } else { + return null; + } + }, + }, + Mount: ReactNativeMount, + Reconciler: require('ReactReconciler'), + }); +} + +module.exports = ReactNative; diff --git a/src/renderers/native/ReactNativeStackInjection.js b/src/renderers/native/ReactNativeStackInjection.js index 8b85a67e9bd5a..90b912c0204ee 100644 --- a/src/renderers/native/ReactNativeStackInjection.js +++ b/src/renderers/native/ReactNativeStackInjection.js @@ -30,6 +30,7 @@ var ReactNativeTextComponent = require('ReactNativeTextComponent'); var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); +var findNodeHandle = require('findNodeHandle'); var invariant = require('invariant'); function inject() { @@ -61,6 +62,13 @@ function inject() { ); }; + findNodeHandle.injection.injectFindNode( + (instance) => instance.getHostNode() + ); + findNodeHandle.injection.injectFindRootNodeID( + (instance) => instance._rootNodeID + ); + ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent); ReactHostComponent.injection.injectTextComponentClass( diff --git a/src/renderers/native/ReactNativeViewConfigRegistry.js b/src/renderers/native/ReactNativeViewConfigRegistry.js new file mode 100644 index 0000000000000..383ee8bba2a20 --- /dev/null +++ b/src/renderers/native/ReactNativeViewConfigRegistry.js @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactNativeViewConfigRegistry + * @flow + */ + +'use strict'; + +const invariant = require('invariant'); + +export type ReactNativeBaseComponentViewConfig = { + validAttributes: Object, + uiViewClassName: string, + propTypes?: Object, +}; + +const viewConfigs = new Map(); + +const prefix = 'topsecret-'; + +const ReactNativeViewConfigRegistry = { + register(viewConfig : ReactNativeBaseComponentViewConfig) { + const name = viewConfig.uiViewClassName; + invariant( + !viewConfigs.has(name), + 'Tried to register two views with the same name %s', + name + ); + const secretName = prefix + name; + viewConfigs.set(secretName, viewConfig); + return secretName; + }, + get(secretName: string) { + const config = viewConfigs.get(secretName); + invariant( + config, + 'View config not found for name %s', + secretName + ); + return config; + }, +}; + +module.exports = ReactNativeViewConfigRegistry; diff --git a/src/renderers/native/__mocks__/ReactNative.js b/src/renderers/native/__mocks__/ReactNative.js new file mode 100644 index 0000000000000..ce5f6050387ae --- /dev/null +++ b/src/renderers/native/__mocks__/ReactNative.js @@ -0,0 +1,16 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); + +module.exports = ReactNativeFeatureFlags.useFiber + ? require('ReactNativeFiber') + : require('ReactNativeStack'); diff --git a/src/renderers/native/createReactNativeComponentClass.js b/src/renderers/native/createReactNativeComponentClass.js index e5f113bf0bbe3..0d560c0553cd8 100644 --- a/src/renderers/native/createReactNativeComponentClass.js +++ b/src/renderers/native/createReactNativeComponentClass.js @@ -12,23 +12,35 @@ 'use strict'; -var ReactNativeBaseComponent = require('ReactNativeBaseComponent'); +const ReactNativeBaseComponent = require('ReactNativeBaseComponent'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); +const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); // See also ReactNativeBaseComponent type ReactNativeBaseComponentViewConfig = { validAttributes: Object, uiViewClassName: string, propTypes?: Object, -} +}; + +/** + * @param {string} config iOS View configuration. + * @private + */ +const createReactNativeFiberComponentClass = function( + viewConfig: ReactNativeBaseComponentViewConfig +): string { + return ReactNativeViewConfigRegistry.register(viewConfig); +}; /** * @param {string} config iOS View configuration. * @private */ -var createReactNativeComponentClass = function( +const createReactNativeComponentClass = function( viewConfig: ReactNativeBaseComponentViewConfig ): ReactClass { - var Constructor = function(element) { + const Constructor = function(element) { this._currentElement = element; this._topLevelWrapper = null; this._hostParent = null; @@ -45,4 +57,6 @@ var createReactNativeComponentClass = function( return ((Constructor: any): ReactClass); }; -module.exports = createReactNativeComponentClass; +module.exports = ReactNativeFeatureFlags.useFiber + ? createReactNativeFiberComponentClass + : createReactNativeComponentClass; diff --git a/src/renderers/native/findNodeHandle.js b/src/renderers/native/findNodeHandle.js index aca064b52954a..345700f1c0920 100644 --- a/src/renderers/native/findNodeHandle.js +++ b/src/renderers/native/findNodeHandle.js @@ -50,6 +50,9 @@ import type { ReactInstance } from 'ReactInstanceType'; * nodeHandle N/A rootNodeID tag */ +let injectedFindNode; +let injectedFindRootNodeID; + function findNodeHandle(componentOrHandle: any): ?number { if (__DEV__) { // TODO: fix this unsafe cast to work with Fiber. @@ -82,9 +85,9 @@ function findNodeHandle(componentOrHandle: any): ?number { // ReactInstanceMap.get here will always succeed for mounted components var internalInstance = ReactInstanceMap.get(component); if (internalInstance) { - return internalInstance.getHostNode(); + return injectedFindNode(internalInstance); } else { - var rootNodeID = component._rootNodeID; + var rootNodeID = injectedFindRootNodeID(component); if (rootNodeID) { return rootNodeID; } else { @@ -92,7 +95,10 @@ function findNodeHandle(componentOrHandle: any): ?number { ( // Native typeof component === 'object' && - '_rootNodeID' in component + ( + '_rootNodeID' in component || // TODO (bvaughn) Clean up once Stack is deprecated + '_nativeTag' in component + ) ) || ( // Composite component.render != null && @@ -112,4 +118,14 @@ function findNodeHandle(componentOrHandle: any): ?number { } } +// Fiber and stack implementations differ; each must inject a strategy +findNodeHandle.injection = { + injectFindNode(findNode) { + injectedFindNode = findNode; + }, + injectFindRootNodeID(findRootNodeID) { + injectedFindRootNodeID = findRootNodeID; + }, +}; + module.exports = findNodeHandle;