Skip to content

Commit

Permalink
RN: Simpify requireNativeComponent
Browse files Browse the repository at this point in the history
Reviewed By: sahrens

Differential Revision: D7893592

fbshipit-source-id: bfe7772ff2fa785fc7c5ad1f7dc0dbe97b6ffb11
  • Loading branch information
yungsters authored and facebook-github-bot committed May 7, 2018
1 parent 28d3778 commit b549e36
Showing 1 changed file with 85 additions and 78 deletions.
163 changes: 85 additions & 78 deletions Libraries/ReactNative/requireNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @flow
* @format
*/

'use strict';

const Platform = require('Platform');
Expand All @@ -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
Expand All @@ -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];
Expand All @@ -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) {
Expand All @@ -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(
Expand Down

0 comments on commit b549e36

Please sign in to comment.