diff --git a/Libraries/ReactNative/requireNativeComponent.js b/Libraries/ReactNative/requireNativeComponent.js index dd44ea60b..fae553f27 100644 --- a/Libraries/ReactNative/requireNativeComponent.js +++ b/Libraries/ReactNative/requireNativeComponent.js @@ -7,6 +7,7 @@ * @flow * @format */ + 'use strict'; const Platform = require('Platform'); @@ -24,6 +25,14 @@ const verifyPropTypes = require('verifyPropTypes'); const invariant = require('fbjs/lib/invariant'); const warning = require('fbjs/lib/warning'); +import type {ComponentInterface} from 'verifyPropTypes'; + +type ExtraOptions = $ReadOnly<{| + nativeOnly?: $ReadOnly<{ + [propName: string]: boolean, + }>, +|}>; + /** * Used to create React components that directly wrap native component * implementations. Config information is extracted from data exported from the @@ -39,81 +48,23 @@ const warning = require('fbjs/lib/warning'); * Common types are lined up with the appropriate prop differs with * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`. */ -import type {ComponentInterface} from 'verifyPropTypes'; - -let hasAttachedDefaultEventTypes: boolean = false; - -function requireNativeComponent( +const requireNativeComponent = ( viewName: string, componentInterface?: ?ComponentInterface, - extraConfig?: ?{nativeOnly?: Object}, -): React$ComponentType | string { - function attachDefaultEventTypes(viewConfig: any) { - // This is supported on UIManager platforms (ex: Android), - // as lazy view managers are not implemented for all platforms. - // See [UIManager] for details on constants and implementations. - if (UIManager.ViewManagerNames) { - // Lazy view managers enabled. - viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes()); - } else { - viewConfig.bubblingEventTypes = merge( - viewConfig.bubblingEventTypes, - UIManager.genericBubblingEventTypes, - ); - viewConfig.directEventTypes = merge( - viewConfig.directEventTypes, - UIManager.genericDirectEventTypes, - ); - } - } - - function merge(destination: ?Object, source: ?Object): ?Object { - if (!source) { - return destination; - } - if (!destination) { - return source; - } - - for (const key in source) { - if (!source.hasOwnProperty(key)) { - continue; - } - - var sourceValue = source[key]; - if (destination.hasOwnProperty(key)) { - const destinationValue = destination[key]; - if ( - typeof sourceValue === 'object' && - typeof destinationValue === 'object' - ) { - sourceValue = merge(destinationValue, sourceValue); - } - } - destination[key] = sourceValue; - } - return destination; - } - - // Don't load the ViewConfig from UIManager until it's needed for rendering. - // Lazy-loading this can help avoid Prepack deopts. - function getViewConfig() { + extraConfig?: ?ExtraOptions, +): string => + createReactNativeComponentClass(viewName, () => { const viewConfig = UIManager[viewName]; invariant( viewConfig != null && viewConfig.NativeProps != null, - 'Native component for "%s" does not exist', + 'requireNativeComponent: "%s" was not found in the UIManager.', viewName, ); - viewConfig.uiViewClassName = viewName; - viewConfig.validAttributes = {}; - viewConfig.propTypes = - componentInterface == null ? null : componentInterface.propTypes; - - let baseModuleName = viewConfig.baseModuleName; - let bubblingEventTypes = viewConfig.bubblingEventTypes; - let directEventTypes = viewConfig.directEventTypes; + // TODO: This seems like a whole lot of runtime initialization for every + // native component that can be either avoided or simplified. + let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig; let nativeProps = viewConfig.NativeProps; while (baseModuleName) { const baseModule = UIManager[baseModuleName]; @@ -137,32 +88,40 @@ function requireNativeComponent( } } - viewConfig.bubblingEventTypes = bubblingEventTypes; - viewConfig.directEventTypes = directEventTypes; + const viewAttributes = {}; for (const key in nativeProps) { const typeName = nativeProps[key]; const diff = getDifferForType(typeName); const process = getProcessorForType(typeName); - viewConfig.validAttributes[key] = + viewAttributes[key] = diff == null && process == null ? true : {diff, process}; } - // Unfortunately, the current set up puts the style properties on the top - // level props object. We also need to add the nested form for API - // compatibility. This allows these props on both the top level and the - // nested style level. TODO: Move these to nested declarations on the - // native side. - viewConfig.validAttributes.style = ReactNativeStyleAttributes; + // Unfortunately, the current setup declares style properties as top-level + // props. This makes it so we allow style properties in the `style` prop. + // TODO: Move style properties into a `style` prop and disallow them as + // top-level props on the native side. + viewAttributes.style = ReactNativeStyleAttributes; + + Object.assign(viewConfig, { + uiViewClassName: viewName, + validAttributes: viewAttributes, + propTypes: + componentInterface == null ? null : componentInterface.propTypes, + bubblingEventTypes, + directEventTypes, + }); if (__DEV__) { - componentInterface && + if (componentInterface != null) { verifyPropTypes( componentInterface, viewConfig, - extraConfig && extraConfig.nativeOnly, + extraConfig == null ? null : extraConfig.nativeOnly, ); + } } if (!hasAttachedDefaultEventTypes) { @@ -171,9 +130,57 @@ function requireNativeComponent( } return viewConfig; + }); + +// TODO: Figure out how this makes sense. We're using a global boolean to only +// initialize this on the first eagerly initialized native component. +let hasAttachedDefaultEventTypes = false; +function attachDefaultEventTypes(viewConfig: any) { + // This is supported on UIManager platforms (ex: Android), + // as lazy view managers are not implemented for all platforms. + // See [UIManager] for details on constants and implementations. + if (UIManager.ViewManagerNames) { + // Lazy view managers enabled. + viewConfig = merge(viewConfig, UIManager.getDefaultEventTypes()); + } else { + viewConfig.bubblingEventTypes = merge( + viewConfig.bubblingEventTypes, + UIManager.genericBubblingEventTypes, + ); + viewConfig.directEventTypes = merge( + viewConfig.directEventTypes, + UIManager.genericDirectEventTypes, + ); + } +} + +// TODO: Figure out how to avoid all this runtime initialization cost. +function merge(destination: ?Object, source: ?Object): ?Object { + if (!source) { + return destination; + } + if (!destination) { + return source; } - return createReactNativeComponentClass(viewName, getViewConfig); + for (const key in source) { + if (!source.hasOwnProperty(key)) { + continue; + } + + let sourceValue = source[key]; + if (destination.hasOwnProperty(key)) { + const destinationValue = destination[key]; + if ( + typeof sourceValue === 'object' && + typeof destinationValue === 'object' + ) { + sourceValue = merge(destinationValue, sourceValue); + } + } + destination[key] = sourceValue; + } + return destination; } function getDifferForType(