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
* @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<any> | 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,33 +88,41 @@ 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) {
attachDefaultEventTypes(viewConfig);
@ -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,
);
}
}
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(