RN: Simpify `requireNativeComponent`

Reviewed By: sahrens

Differential Revision: D7893592

fbshipit-source-id: bfe7772ff2fa785fc7c5ad1f7dc0dbe97b6ffb11
This commit is contained in:
Tim Yung 2018-05-07 16:45:16 -07:00 committed by Facebook Github Bot
parent 28d37781c6
commit b549e364e0
1 changed files with 85 additions and 78 deletions

View File

@ -7,6 +7,7 @@
* @flow * @flow
* @format * @format
*/ */
'use strict'; 'use strict';
const Platform = require('Platform'); const Platform = require('Platform');
@ -24,6 +25,14 @@ const verifyPropTypes = require('verifyPropTypes');
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning'); 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 * Used to create React components that directly wrap native component
* implementations. Config information is extracted from data exported from the * 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 * Common types are lined up with the appropriate prop differs with
* `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`. * `TypeToDifferMap`. Non-scalar types not in the map default to `deepDiffer`.
*/ */
import type {ComponentInterface} from 'verifyPropTypes'; const requireNativeComponent = (
let hasAttachedDefaultEventTypes: boolean = false;
function requireNativeComponent(
viewName: string, viewName: string,
componentInterface?: ?ComponentInterface, componentInterface?: ?ComponentInterface,
extraConfig?: ?{nativeOnly?: Object}, extraConfig?: ?ExtraOptions,
): React$ComponentType<any> | string { ): string =>
function attachDefaultEventTypes(viewConfig: any) { createReactNativeComponentClass(viewName, () => {
// 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() {
const viewConfig = UIManager[viewName]; const viewConfig = UIManager[viewName];
invariant( invariant(
viewConfig != null && viewConfig.NativeProps != null, viewConfig != null && viewConfig.NativeProps != null,
'Native component for "%s" does not exist', 'requireNativeComponent: "%s" was not found in the UIManager.',
viewName, viewName,
); );
viewConfig.uiViewClassName = viewName; // TODO: This seems like a whole lot of runtime initialization for every
viewConfig.validAttributes = {}; // native component that can be either avoided or simplified.
viewConfig.propTypes = let {baseModuleName, bubblingEventTypes, directEventTypes} = viewConfig;
componentInterface == null ? null : componentInterface.propTypes;
let baseModuleName = viewConfig.baseModuleName;
let bubblingEventTypes = viewConfig.bubblingEventTypes;
let directEventTypes = viewConfig.directEventTypes;
let nativeProps = viewConfig.NativeProps; let nativeProps = viewConfig.NativeProps;
while (baseModuleName) { while (baseModuleName) {
const baseModule = UIManager[baseModuleName]; const baseModule = UIManager[baseModuleName];
@ -137,33 +88,41 @@ function requireNativeComponent(
} }
} }
viewConfig.bubblingEventTypes = bubblingEventTypes; const viewAttributes = {};
viewConfig.directEventTypes = directEventTypes;
for (const key in nativeProps) { for (const key in nativeProps) {
const typeName = nativeProps[key]; const typeName = nativeProps[key];
const diff = getDifferForType(typeName); const diff = getDifferForType(typeName);
const process = getProcessorForType(typeName); const process = getProcessorForType(typeName);
viewConfig.validAttributes[key] = viewAttributes[key] =
diff == null && process == null ? true : {diff, process}; diff == null && process == null ? true : {diff, process};
} }
// Unfortunately, the current set up puts the style properties on the top // Unfortunately, the current setup declares style properties as top-level
// level props object. We also need to add the nested form for API // props. This makes it so we allow style properties in the `style` prop.
// compatibility. This allows these props on both the top level and the // TODO: Move style properties into a `style` prop and disallow them as
// nested style level. TODO: Move these to nested declarations on the // top-level props on the native side.
// native side. viewAttributes.style = ReactNativeStyleAttributes;
viewConfig.validAttributes.style = ReactNativeStyleAttributes;
Object.assign(viewConfig, {
uiViewClassName: viewName,
validAttributes: viewAttributes,
propTypes:
componentInterface == null ? null : componentInterface.propTypes,
bubblingEventTypes,
directEventTypes,
});
if (__DEV__) { if (__DEV__) {
componentInterface && if (componentInterface != null) {
verifyPropTypes( verifyPropTypes(
componentInterface, componentInterface,
viewConfig, viewConfig,
extraConfig && extraConfig.nativeOnly, extraConfig == null ? null : extraConfig.nativeOnly,
); );
} }
}
if (!hasAttachedDefaultEventTypes) { if (!hasAttachedDefaultEventTypes) {
attachDefaultEventTypes(viewConfig); attachDefaultEventTypes(viewConfig);
@ -171,9 +130,57 @@ function requireNativeComponent(
} }
return viewConfig; 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,
);
}
} }
return createReactNativeComponentClass(viewName, getViewConfig); // 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;
}
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( function getDifferForType(