From 9344f3a95b56833d29cd18438a94a0c22f67b0f8 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 20 Mar 2017 12:58:08 -0700 Subject: [PATCH] Support string return type from RN createReactNativeFiberComponentClass() Reviewed By: sebmarkbage Differential Revision: D4607283 fbshipit-source-id: 466d2373dd570f77ebcced306d2f20a3f72d79c6 --- Libraries/Components/TextInput/TextInput.js | 7 - Libraries/Components/View/View.js | 19 +- Libraries/ReactNative/UIManager.js | 23 +- .../ReactNative/requireNativeComponent.js | 2 +- .../renderers/native/NativeMethodsMixin.js | 206 +++++++++++------- .../native/NativeMethodsMixinUtils.js | 100 +++++++++ .../src/renderers/native/ReactNative.js | 2 +- .../src/renderers/native/ReactNativeFiber.js | 44 ++-- .../native/ReactNativeFiberHostComponent.js | 108 +++++++++ .../src/renderers/native/ReactNativeStack.js | 18 +- .../native/ReactNativeStackInjection.js | 4 +- .../native/createReactNativeComponentClass.js | 16 +- .../src/renderers/native/findNodeHandle.js | 5 +- 13 files changed, 424 insertions(+), 130 deletions(-) create mode 100644 Libraries/Renderer/src/renderers/native/NativeMethodsMixinUtils.js create mode 100644 Libraries/Renderer/src/renderers/native/ReactNativeFiberHostComponent.js diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index c3c9eff2b..3f01022cb 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -540,13 +540,6 @@ const TextInput = React.createClass({ */ mixins: [NativeMethodsMixin, TimerMixin], - viewConfig: - ((Platform.OS === 'ios' && RCTTextField ? - RCTTextField.viewConfig : - (Platform.OS === 'android' && AndroidTextInput ? - AndroidTextInput.viewConfig : - {})) : Object), - /** * Returns `true` if the input is currently focused; `false` otherwise. */ diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index a519dfbb6..385ffb124 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -16,6 +16,7 @@ const NativeMethodsMixin = require('NativeMethodsMixin'); const NativeModules = require('NativeModules'); const Platform = require('Platform'); const React = require('React'); +const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes'); const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const StyleSheetPropType = require('StyleSheetPropType'); @@ -119,6 +120,9 @@ const View = React.createClass({ ...statics, }, + // TODO (bvaughn) Replace this with a deprecated getter warning. This object + // should be accessible via a separate import. It will not be available in + // production mode in the future and so should not be directly accessed. propTypes: { ...TVViewPropTypes, @@ -536,11 +540,20 @@ if (__DEV__) { } } +// TODO (bvaughn) Remove feature flags once all static View accessors are gone. +// We temporarily wrap fiber native views with the create-class View above, +// Because external code sometimes accesses static properties of this view. let ViewToExport = RCTView; -if (__DEV__) { +if ( + __DEV__ || + ReactNativeFeatureFlags.useFiber +) { ViewToExport = View; } else { - Object.assign(RCTView, statics); + // TODO (bvaughn) Remove this mixin once all static View accessors are gone. + Object.assign((RCTView : any), statics); } -module.exports = ViewToExport; +// TODO (bvaughn) Temporarily mask Flow warnings for View property accesses. +// We're wrapping the string type (Fiber) for now to avoid any actual problems. +module.exports = ((ViewToExport : any) : typeof View); diff --git a/Libraries/ReactNative/UIManager.js b/Libraries/ReactNative/UIManager.js index ee2fc4b86..3b131de70 100644 --- a/Libraries/ReactNative/UIManager.js +++ b/Libraries/ReactNative/UIManager.js @@ -26,6 +26,27 @@ invariant(UIManager, 'UIManager is undefined. The native module config is probab const _takeSnapshot = UIManager.takeSnapshot; +// findNodeHandle() returns a reference to a wrapper component with viewConfig. +// This wrapper is required for NativeMethodsMixin.setNativeProps, but most +// callers want the native tag (number) and not the wrapper. For this purpose, +// the ReactNative renderer decorates findNodeHandle() and extracts the tag. +// However UIManager can't require ReactNative without introducing a cycle, and +// deferring the require causes a significant performance regression in Wilde +// (along the lines of 17% regression in RN Bridge startup). So as a temporary +// workaround, this wrapper method mimics what the native renderer does. +// TODO (bvaughn) Remove this and use findNodeHandle directly once stack is gone +function findNodeHandleWrapper(componentOrHandle : any) : ?number { + const instance: any = findNodeHandle(componentOrHandle); + + if (instance) { + return typeof instance._nativeTag === 'number' + ? instance._nativeTag + : instance.getHostNode(); + } else { + return null; + } +} + /** * Capture an image of the screen, window or an individual view. The image * will be stored in a temporary file that will only exist for as long as the @@ -57,7 +78,7 @@ UIManager.takeSnapshot = async function( return; } if (typeof view !== 'number' && view !== 'window') { - view = findNodeHandle(view) || 'window'; + view = findNodeHandleWrapper(view) || 'window'; } return _takeSnapshot(view, options); }; diff --git a/Libraries/ReactNative/requireNativeComponent.js b/Libraries/ReactNative/requireNativeComponent.js index c404e60bf..682a8240c 100644 --- a/Libraries/ReactNative/requireNativeComponent.js +++ b/Libraries/ReactNative/requireNativeComponent.js @@ -46,7 +46,7 @@ function requireNativeComponent( viewName: string, componentInterface?: ?ComponentInterface, extraConfig?: ?{nativeOnly?: Object}, -): Function { +): ReactClass | string { const viewConfig = UIManager[viewName]; if (!viewConfig || !viewConfig.NativeProps) { warning(false, 'Native component for "%s" does not exist', viewName); diff --git a/Libraries/Renderer/src/renderers/native/NativeMethodsMixin.js b/Libraries/Renderer/src/renderers/native/NativeMethodsMixin.js index aa7af7f06..f3d0570a2 100644 --- a/Libraries/Renderer/src/renderers/native/NativeMethodsMixin.js +++ b/Libraries/Renderer/src/renderers/native/NativeMethodsMixin.js @@ -13,45 +13,25 @@ var ReactNative = require('ReactNative'); var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); var TextInputState = require('TextInputState'); var UIManager = require('UIManager'); var invariant = require('fbjs/lib/invariant'); +var findNodeHandle = require('findNodeHandle'); -type MeasureOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, - pageX: number, - pageY: number -) => void +var { + mountSafeCallback, + throwOnStylesProp, + warnForStyleProps, +} = require('NativeMethodsMixinUtils'); -type MeasureInWindowOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, -) => void - -type MeasureLayoutOnSuccessCallback = ( - left: number, - top: number, - width: number, - height: number -) => void - -function warnForStyleProps(props, validAttributes) { - for (var key in validAttributes.style) { - if (!(validAttributes[key] || props[key] === undefined)) { - console.error( - 'You are setting the style `{ ' + key + ': ... }` as a prop. You ' + - 'should nest it in a style object. ' + - 'E.g. `{ style: { ' + key + ': ... } }`' - ); - } - } -} +import type { + MeasureInWindowOnSuccessCallback, + MeasureLayoutOnSuccessCallback, + MeasureOnSuccessCallback, +} from 'NativeMethodsMixinUtils'; +import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry'; /** * `NativeMethodsMixin` provides methods to access the underlying native @@ -65,6 +45,10 @@ function warnForStyleProps(props, validAttributes) { * information, see [Direct * Manipulation](docs/direct-manipulation.html). */ +// TODO (bvaughn) Figure out how to use the NativeMethodsInterface type to- +// ensure that these mixins and ReactNativeFiberHostComponent stay in sync. +// Unfortunately, using it causes Flow to complain WRT createClass mixins: +// "call of method `createClass`. Expected an exact object instead of ..." var NativeMethodsMixin = { /** * Determines the location on screen, width, and height of the given view and @@ -140,20 +124,15 @@ var NativeMethodsMixin = { * Manipulation](docs/direct-manipulation.html)). */ setNativeProps: function(nativeProps: Object) { - if (__DEV__) { - warnForStyleProps(nativeProps, this.viewConfig.validAttributes); - } + // Ensure ReactNative factory function has configured findNodeHandle. + // Requiring it won't execute the factory function until first referenced. + // It's possible for tests that use ReactTestRenderer to reach this point, + // Without having executed ReactNative. + // Defer the factory function until now to avoid a cycle with UIManager. + // TODO (bvaughn) Remove this once ReactNativeStack is dropped. + require('ReactNative'); - var updatePayload = ReactNativeAttributePayload.create( - nativeProps, - this.viewConfig.validAttributes - ); - - UIManager.updateView( - (ReactNative.findNodeHandle(this) : any), - this.viewConfig.uiViewClassName, - updatePayload - ); + injectedSetNativeProps(this, nativeProps); }, /** @@ -172,19 +151,116 @@ var NativeMethodsMixin = { }, }; -function throwOnStylesProp(component, props) { - if (props.styles !== undefined) { - var owner = component._owner || null; - var name = component.constructor.displayName; - var msg = '`styles` is not a supported property of `' + name + '`, did ' + - 'you mean `style` (singular)?'; - if (owner && owner.constructor && owner.constructor.displayName) { - msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' + - ' component.'; - } - throw new Error(msg); +// TODO (bvaughn) Inline this once ReactNativeStack is dropped. +function setNativePropsFiber(componentOrHandle: any, nativeProps: Object) { + // Class components don't have viewConfig -> validateAttributes. + // Nor does it make sense to set native props on a non-native component. + // Instead, find the nearest host component and set props on it. + // Use findNodeHandle() rather than ReactNative.findNodeHandle() because + // We want the instance/wrapper (not the native tag). + let maybeInstance; + + // Fiber errors if findNodeHandle is called for an umounted component. + // Tests using ReactTestRenderer will trigger this case indirectly. + // Mimicking stack behavior, we should silently ignore this case. + // TODO Fix ReactTestRenderer so we can remove this try/catch. + try { + maybeInstance = findNodeHandle(componentOrHandle); + } catch (error) {} + + // If there is no host component beneath this we should fail silently. + // This is not an error; it could mean a class component rendered null. + if (maybeInstance == null) { + return; } + + const viewConfig : ReactNativeBaseComponentViewConfig = + maybeInstance.viewConfig; + + if (__DEV__) { + warnForStyleProps(nativeProps, viewConfig.validAttributes); + } + + var updatePayload = ReactNativeAttributePayload.create( + nativeProps, + viewConfig.validAttributes, + ); + + UIManager.updateView( + maybeInstance._nativeTag, + viewConfig.uiViewClassName, + updatePayload, + ); } + +// TODO (bvaughn) Remove this once ReactNativeStack is dropped. +function setNativePropsStack(componentOrHandle: any, nativeProps: Object) { + // Class components don't have viewConfig -> validateAttributes. + // Nor does it make sense to set native props on a non-native component. + // Instead, find the nearest host component and set props on it. + // Use findNodeHandle() rather than ReactNative.findNodeHandle() because + // We want the instance/wrapper (not the native tag). + let maybeInstance = findNodeHandle(componentOrHandle); + + // If there is no host component beneath this we should fail silently. + // This is not an error; it could mean a class component rendered null. + if (maybeInstance == null) { + return; + } + + let viewConfig : ReactNativeBaseComponentViewConfig; + if (maybeInstance.viewConfig !== undefined) { + // ReactNativeBaseComponent + viewConfig = maybeInstance.viewConfig; + } else if ( + maybeInstance._instance !== undefined && + maybeInstance._instance.viewConfig !== undefined + ) { + // ReactCompositeComponentWrapper + // Some instances (eg Text) define their own viewConfig + viewConfig = maybeInstance._instance.viewConfig; + } else { + // ReactCompositeComponentWrapper + // Other instances (eg TextInput) defer to their children's viewConfig + while (maybeInstance._renderedComponent !== undefined) { + maybeInstance = maybeInstance._renderedComponent; + } + viewConfig = maybeInstance.viewConfig; + } + + const tag : number = typeof maybeInstance.getHostNode === 'function' + ? maybeInstance.getHostNode() + : maybeInstance._rootNodeID; + + if (__DEV__) { + warnForStyleProps(nativeProps, viewConfig.validAttributes); + } + + var updatePayload = ReactNativeAttributePayload.create( + nativeProps, + viewConfig.validAttributes, + ); + + UIManager.updateView( + tag, + viewConfig.uiViewClassName, + updatePayload, + ); +} + +// Switching based on fiber vs stack to avoid a lot of inline checks at runtime. +// HACK Normally this injection would be done by the renderer, but in this case +// that would result in a cycle between ReactNative and NativeMethodsMixin. +// We avoid requiring additional code for this injection so it's probably ok? +// TODO (bvaughn) Remove this once ReactNativeStack is gone. +let injectedSetNativeProps : + (componentOrHandle: any, nativeProps: Object) => void; +if (ReactNativeFeatureFlags.useFiber) { + injectedSetNativeProps = setNativePropsFiber; +} else { + injectedSetNativeProps = setNativePropsStack; +} + if (__DEV__) { // hide this from Flow since we can't define these properties outside of // __DEV__ without actually implementing them (setting them to undefined @@ -203,20 +279,4 @@ if (__DEV__) { }; } -/** - * In the future, we should cleanup callbacks by cancelling them instead of - * using this. - */ -function mountSafeCallback( - context: ReactComponent, - callback: ?Function -): any { - return function() { - if (!callback || (typeof context.isMounted === 'function' && !context.isMounted())) { - return undefined; - } - return callback.apply(context, arguments); - }; -} - module.exports = NativeMethodsMixin; diff --git a/Libraries/Renderer/src/renderers/native/NativeMethodsMixinUtils.js b/Libraries/Renderer/src/renderers/native/NativeMethodsMixinUtils.js new file mode 100644 index 000000000..b5aa4ae8d --- /dev/null +++ b/Libraries/Renderer/src/renderers/native/NativeMethodsMixinUtils.js @@ -0,0 +1,100 @@ +/** + * 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 NativeMethodsMixinUtils + * @flow + */ +'use strict'; + +export type MeasureOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number +) => void + +export type MeasureInWindowOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, +) => void + +export type MeasureLayoutOnSuccessCallback = ( + left: number, + top: number, + width: number, + height: number +) => void + +/** + * Shared between ReactNativeFiberHostComponent and NativeMethodsMixin to keep + * API in sync. + */ +export interface NativeMethodsInterface { + blur() : void, + focus() : void, + measure(callback : MeasureOnSuccessCallback) : void, + measureInWindow(callback : MeasureInWindowOnSuccessCallback) : void, + measureLayout( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail: () => void /* currently unused */ + ) : void, + setNativeProps(nativeProps: Object) : void, +} + +/** + * In the future, we should cleanup callbacks by cancelling them instead of + * using this. + */ +function mountSafeCallback( + context: any, + callback: ?Function +): any { + return function() { + if (!callback || (typeof context.isMounted === 'function' && !context.isMounted())) { + return undefined; + } + return callback.apply(context, arguments); + }; +} + +function throwOnStylesProp(component : any, props : any) { + if (props.styles !== undefined) { + var owner = component._owner || null; + var name = component.constructor.displayName; + var msg = '`styles` is not a supported property of `' + name + '`, did ' + + 'you mean `style` (singular)?'; + if (owner && owner.constructor && owner.constructor.displayName) { + msg += '\n\nCheck the `' + owner.constructor.displayName + '` parent ' + + ' component.'; + } + throw new Error(msg); + } +} + +function warnForStyleProps(props : any, validAttributes : any) { + for (var key in validAttributes.style) { + if (!(validAttributes[key] || props[key] === undefined)) { + console.error( + 'You are setting the style `{ ' + key + ': ... }` as a prop. You ' + + 'should nest it in a style object. ' + + 'E.g. `{ style: { ' + key + ': ... } }`' + ); + } + } +} + +module.exports = { + mountSafeCallback, + throwOnStylesProp, + warnForStyleProps, +}; diff --git a/Libraries/Renderer/src/renderers/native/ReactNative.js b/Libraries/Renderer/src/renderers/native/ReactNative.js index 527a53920..82b12de9a 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNative.js +++ b/Libraries/Renderer/src/renderers/native/ReactNative.js @@ -15,4 +15,4 @@ const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); module.exports = ReactNativeFeatureFlags.useFiber ? require('ReactNativeFiber') - : require('ReactNativeStack') + : require('ReactNativeStack'); diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js b/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js index 1d73e80b8..e2ad6701a 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeFiber.js @@ -12,16 +12,11 @@ '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 ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent'); const ReactNativeInjection = require('ReactNativeInjection'); const ReactNativeTagHandles = require('ReactNativeTagHandles'); const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); @@ -34,6 +29,11 @@ const findNodeHandle = require('findNodeHandle'); const invariant = require('fbjs/lib/invariant'); const { injectInternals } = require('ReactFiberDevToolsHook'); + +import type { Element } from 'React'; +import type { Fiber } from 'ReactFiber'; +import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry'; +import type { ReactNodeList } from 'ReactTypes'; const { precacheFiberNode, uncacheFiberNode, @@ -43,7 +43,7 @@ const { ReactNativeInjection.inject(); type Container = number; -type Instance = { +export type Instance = { _children: Array, _nativeTag: number, viewConfig: ReactNativeBaseComponentViewConfig, @@ -51,13 +51,6 @@ type Instance = { 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); @@ -156,7 +149,7 @@ const NativeRenderer = ReactFiberReconciler({ const viewConfig = ReactNativeViewConfigRegistry.get(type); if (__DEV__) { - for (let key in viewConfig.validAttributes) { + for (const key in viewConfig.validAttributes) { if (props.hasOwnProperty(key)) { deepFreezeAndThrowOnMutationInDev(props[key]); } @@ -175,12 +168,14 @@ const NativeRenderer = ReactFiberReconciler({ updatePayload, // props ); - const component = new NativeHostComponent(tag, viewConfig); + const component = new ReactNativeFiberHostComponent(tag, viewConfig); precacheFiberNode(internalInstanceHandle, tag); updateFiberProps(tag, props); - return component; + // Not sure how to avoid this cast. Flow is okay if the component is defined + // in the same file but if it's external it can't see the types. + return ((component : any) : Instance); }, createTextInstance( @@ -367,17 +362,20 @@ ReactGenericBatching.injection.injectFiberBatchedUpdates( const roots = new Map(); findNodeHandle.injection.injectFindNode( - (fiber: Fiber) => { - const instance: any = NativeRenderer.findHostInstance(fiber); - return instance ? instance._nativeTag : null; - } + (fiber: Fiber) => NativeRenderer.findHostInstance(fiber) ); findNodeHandle.injection.injectFindRootNodeID( - (instance) => instance._nativeTag + (instance) => instance ); const ReactNative = { - findNodeHandle, + // External users of findNodeHandle() expect the host tag number return type. + // The injected findNodeHandle() strategy returns the instance wrapper though. + // See NativeMethodsMixin#setNativeProps for more info on why this is done. + findNodeHandle(componentOrHandle : any) : ?number { + const instance: any = findNodeHandle(componentOrHandle); + return instance ? instance._nativeTag : null; + }, render(element : Element, containerTag : any, callback: ?Function) { let root = roots.get(containerTag); diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeFiberHostComponent.js b/Libraries/Renderer/src/renderers/native/ReactNativeFiberHostComponent.js new file mode 100644 index 000000000..2935b199d --- /dev/null +++ b/Libraries/Renderer/src/renderers/native/ReactNativeFiberHostComponent.js @@ -0,0 +1,108 @@ +/** + * 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 ReactNativeFiberHostComponent + * @flow + * @preventMunge + */ + +'use strict'; + +var ReactNativeAttributePayload = require('ReactNativeAttributePayload'); +var TextInputState = require('TextInputState'); +var UIManager = require('UIManager'); + +var { + mountSafeCallback, + warnForStyleProps, +} = require('NativeMethodsMixinUtils'); + +import type { + MeasureInWindowOnSuccessCallback, + MeasureLayoutOnSuccessCallback, + MeasureOnSuccessCallback, + NativeMethodsInterface, +} from 'NativeMethodsMixinUtils'; +import type { Instance } from 'ReactNativeFiber'; +import type { ReactNativeBaseComponentViewConfig } from 'ReactNativeViewConfigRegistry'; + +/** + * This component defines the same methods as NativeMethodsMixin but without the + * findNodeHandle wrapper. This wrapper is unnecessary for HostComponent views + * and would also result in a circular require.js dependency (since + * ReactNativeFiber depends on this component and NativeMethodsMixin depends on + * ReactNativeFiber). + */ +class ReactNativeFiberHostComponent implements NativeMethodsInterface { + _children: Array + _nativeTag: number + viewConfig: ReactNativeBaseComponentViewConfig + + constructor( + tag : number, + viewConfig : ReactNativeBaseComponentViewConfig + ) { + this._nativeTag = tag; + this._children = []; + this.viewConfig = viewConfig; + } + + blur() { + TextInputState.blurTextInput(this._nativeTag); + } + + focus() { + TextInputState.focusTextInput(this._nativeTag); + } + + measure(callback: MeasureOnSuccessCallback) { + UIManager.measure( + this._nativeTag, + mountSafeCallback(this, callback) + ); + } + + measureInWindow(callback: MeasureInWindowOnSuccessCallback) { + UIManager.measureInWindow( + this._nativeTag, + mountSafeCallback(this, callback) + ); + } + + measureLayout( + relativeToNativeNode: number, + onSuccess: MeasureLayoutOnSuccessCallback, + onFail: () => void /* currently unused */ + ) { + UIManager.measureLayout( + this._nativeTag, + relativeToNativeNode, + mountSafeCallback(this, onFail), + mountSafeCallback(this, onSuccess) + ); + } + + setNativeProps(nativeProps: Object) { + if (__DEV__) { + warnForStyleProps(nativeProps, this.viewConfig.validAttributes); + } + + var updatePayload = ReactNativeAttributePayload.create( + nativeProps, + this.viewConfig.validAttributes + ); + + UIManager.updateView( + this._nativeTag, + this.viewConfig.uiViewClassName, + updatePayload + ); + } +} + +module.exports = ReactNativeFiberHostComponent; diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeStack.js b/Libraries/Renderer/src/renderers/native/ReactNativeStack.js index 55cf71fa1..d6ae88792 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeStack.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeStack.js @@ -13,8 +13,8 @@ var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeInjection = require('ReactNativeInjection'); -var ReactNativeStackInjection = require('ReactNativeStackInjection'); var ReactNativeMount = require('ReactNativeMount'); +var ReactNativeStackInjection = require('ReactNativeStackInjection'); var ReactUpdates = require('ReactUpdates'); var findNodeHandle = require('findNodeHandle'); @@ -30,16 +30,16 @@ var render = function( return ReactNativeMount.renderComponent(element, mountInto, callback); }; -findNodeHandle.injection.injectFindNode( - (instance) => instance.getHostNode() -); -findNodeHandle.injection.injectFindRootNodeID( - (instance) => instance._rootNodeID -); - var ReactNative = { hasReactNativeInitialized: false, - findNodeHandle: findNodeHandle, + + // External users of findNodeHandle() expect the host tag number return type. + // The injected findNodeHandle() strategy returns the instance wrapper though. + // See NativeMethodsMixin#setNativeProps for more info on why this is done. + findNodeHandle(componentOrHandle : any) : ?number { + return findNodeHandle(componentOrHandle).getHostNode(); + }, + render: render, unmountComponentAtNode: ReactNativeMount.unmountComponentAtNode, diff --git a/Libraries/Renderer/src/renderers/native/ReactNativeStackInjection.js b/Libraries/Renderer/src/renderers/native/ReactNativeStackInjection.js index fe4f4cd4c..cc1f812a6 100644 --- a/Libraries/Renderer/src/renderers/native/ReactNativeStackInjection.js +++ b/Libraries/Renderer/src/renderers/native/ReactNativeStackInjection.js @@ -63,10 +63,10 @@ function inject() { }; findNodeHandle.injection.injectFindNode( - (instance) => instance.getHostNode() + (instance) => instance ); findNodeHandle.injection.injectFindRootNodeID( - (instance) => instance._rootNodeID + (instance) => instance ); ReactEmptyComponent.injection.injectEmptyComponentFactory(EmptyComponent); diff --git a/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js b/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js index bf53cf816..3549c750f 100644 --- a/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js +++ b/Libraries/Renderer/src/renderers/native/createReactNativeComponentClass.js @@ -13,8 +13,8 @@ 'use strict'; const ReactNativeBaseComponent = require('ReactNativeBaseComponent'); -const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); const ReactNativeFeatureFlags = require('ReactNativeFeatureFlags'); +const ReactNativeViewConfigRegistry = require('ReactNativeViewConfigRegistry'); // See also ReactNativeBaseComponent type ReactNativeBaseComponentViewConfig = { @@ -27,14 +27,12 @@ type ReactNativeBaseComponentViewConfig = { * @param {string} config iOS View configuration. * @private */ - const createReactNativeFiberComponentClass = function( - viewConfig: ReactNativeBaseComponentViewConfig - ): ReactClass { - // TODO(sema): This actually returns a string. Need to fix this before - // we deploy Fiber. - return (ReactNativeViewConfigRegistry.register(viewConfig) : any); - }; - +const createReactNativeFiberComponentClass = function( + viewConfig: ReactNativeBaseComponentViewConfig +): string { + return ReactNativeViewConfigRegistry.register(viewConfig); +}; + /** * @param {string} config iOS View configuration. * @private diff --git a/Libraries/Renderer/src/renderers/native/findNodeHandle.js b/Libraries/Renderer/src/renderers/native/findNodeHandle.js index 7edb37892..2230ee37b 100644 --- a/Libraries/Renderer/src/renderers/native/findNodeHandle.js +++ b/Libraries/Renderer/src/renderers/native/findNodeHandle.js @@ -53,7 +53,10 @@ import type { ReactInstance } from 'ReactInstanceType'; let injectedFindNode; let injectedFindRootNodeID; -function findNodeHandle(componentOrHandle: any): ?number { +// TODO (bvaughn) Rename the findNodeHandle module to something more descriptive +// eg findInternalHostInstance. This will reduce the likelihood of someone +// accidentally deep-requiring this version. +function findNodeHandle(componentOrHandle: any): any { if (__DEV__) { // TODO: fix this unsafe cast to work with Fiber. var owner = ((ReactCurrentOwner.current: any): ReactInstance | null);