-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defer reading native view config from UIManager until view is used #10569
Defer reading native view config from UIManager until view is used #10569
Conversation
Don't react viewConfig from UIManager when a view is registered, but only when it is used for the first time. This will help unblock Prepack optimizations for RN views.
Also exposed the native view config registry to allow invalid/unsupported host components to fall back to RCTView
…OT_USE_OR_YOU_WILL_BE_FIRED After discussion with Spencer Ahrens and Andy Street, it seems the early fallback behavior was mostly for the early days of Ads Manager when Android didn't yet support a lot of the view types. It should be okay now to use an invarient/redbox instead for unsupported views.
I don't think I fully understand how lazilyCreateReactNativeComponentClass will get used and how it relates to the non-lazy version. (For example, why not make them all lazy?) |
Fair question! I left a few inline comments about this but probably didn't go into enough detail. Some RN views (eg I was kind of on the fence about it so your thoughts are welcome. 😄 |
I think it makes sense to just have the one API. Can you post the react-native diff too? |
It's posted internally, on my Phabricator. |
Okay. I've removed the non-lazy version of |
The full diff is at 5626312. Beyond the rote changes to replace sync const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
const UIManager = require('UIManager');
-const UnimplementedView = require('UnimplementedView');
const createReactNativeComponentClass = require('createReactNativeComponentClass');
const insetsDiffer = require('insetsDiffer');
@@ -23,6 +22,7 @@
const resolveAssetSource = require('resolveAssetSource');
const sizesDiffer = require('sizesDiffer');
const verifyPropTypes = require('verifyPropTypes');
+const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning');
/**
@@ -47,77 +47,86 @@
componentInterface?: ?ComponentInterface,
extraConfig?: ?{nativeOnly?: Object},
): React$ComponentType<any> | string {
- const viewConfig = UIManager[viewName];
- if (!viewConfig || !viewConfig.NativeProps) {
- warning(false, 'Native component for "%s" does not exist', viewName);
- return UnimplementedView;
- }
+ // 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];
+
+ invariant(
+ viewConfig != null &&
+ !viewConfig.NativeProps != null,
+ 'Native component for "%s" does not exist',
+ viewName
+ );
- viewConfig.uiViewClassName = viewName;
- viewConfig.validAttributes = {};
+ viewConfig.uiViewClassName = viewName;
+ viewConfig.validAttributes = {};
+
+ // ReactNative `View.propTypes` have been deprecated in favor of
+ // `ViewPropTypes`. In their place a temporary getter has been added with a
+ // deprecated warning message. Avoid triggering that warning here by using
+ // temporary workaround, __propTypesSecretDontUseThesePlease.
+ // TODO (bvaughn) Revert this particular change any time after April 1
+ if (componentInterface) {
+ viewConfig.propTypes =
+ typeof componentInterface.__propTypesSecretDontUseThesePlease === 'object'
+ ? componentInterface.__propTypesSecretDontUseThesePlease
+ : componentInterface.propTypes;
+ } else {
+ viewConfig.propTypes = null;
+ }
- // ReactNative `View.propTypes` have been deprecated in favor of
- // `ViewPropTypes`. In their place a temporary getter has been added with a
- // deprecated warning message. Avoid triggering that warning here by using
- // temporary workaround, __propTypesSecretDontUseThesePlease.
- // TODO (bvaughn) Revert this particular change any time after April 1
- if (componentInterface) {
- viewConfig.propTypes =
- typeof componentInterface.__propTypesSecretDontUseThesePlease === 'object'
- ? componentInterface.__propTypesSecretDontUseThesePlease
- : componentInterface.propTypes;
- } else {
- viewConfig.propTypes = null;
- }
+ let baseModuleName = viewConfig.baseModuleName;
+ let nativeProps = { ...viewConfig.NativeProps };
+ while (baseModuleName) {
+ const baseModule = UIManager[baseModuleName];
+ if (!baseModule) {
+ warning(false, 'Base module "%s" does not exist', baseModuleName);
+ baseModuleName = null;
+ } else {
+ nativeProps = { ...nativeProps, ...baseModule.NativeProps };
+ baseModuleName = baseModule.baseModuleName;
+ }
+ }
+
+ for (const key in nativeProps) {
+ let useAttribute = false;
+ const attribute = {};
+
+ const differ = TypeToDifferMap[nativeProps[key]];
+ if (differ) {
+ attribute.diff = differ;
+ useAttribute = true;
+ }
- let baseModuleName = viewConfig.baseModuleName;
- let nativeProps = { ...viewConfig.NativeProps };
- while (baseModuleName) {
- const baseModule = UIManager[baseModuleName];
- if (!baseModule) {
- warning(false, 'Base module "%s" does not exist', baseModuleName);
- baseModuleName = null;
- } else {
- nativeProps = { ...nativeProps, ...baseModule.NativeProps };
- baseModuleName = baseModule.baseModuleName;
+ const processor = TypeToProcessorMap[nativeProps[key]];
+ if (processor) {
+ attribute.process = processor;
+ useAttribute = true;
+ }
+
+ viewConfig.validAttributes[key] = useAttribute ? attribute : true;
}
+
+ // 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;
+
+ if (__DEV__) {
+ componentInterface && verifyPropTypes(
+ componentInterface,
+ viewConfig,
+ extraConfig && extraConfig.nativeOnly
+ );
+ }
+
+ return viewConfig;
}
- for (const key in nativeProps) {
- let useAttribute = false;
- const attribute = {};
-
- const differ = TypeToDifferMap[nativeProps[key]];
- if (differ) {
- attribute.diff = differ;
- useAttribute = true;
- }
-
- const processor = TypeToProcessorMap[nativeProps[key]];
- if (processor) {
- attribute.process = processor;
- useAttribute = true;
- }
-
- viewConfig.validAttributes[key] = useAttribute ? attribute : true;
- }
-
- // 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;
-
- if (__DEV__) {
- componentInterface && verifyPropTypes(
- componentInterface,
- viewConfig,
- extraConfig && extraConfig.nativeOnly
- );
- }
-
- return createReactNativeComponentClass(viewConfig);
+ return createReactNativeComponentClass(viewName, getViewConfig);
}
const TypeToDifferMap = { |
There are only a handful of components with JavaScript-defined view configs. It's probably worth making them use the new lazy/async interface as well to be more consistent.
7eae2aa
to
32db4bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks!
'View config not found for name %s', | ||
name, | ||
); | ||
viewConfigCallbacks.set(name, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason this isn't .delete? Either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use viewConfigCallbacks
to check for view name uniqueness. Nulling out prevents us from blocking GC while avoiding having to check both viewConfigs
and viewConfigCallbacks
maps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Reading them from
UIManager
during registration forces a Prepack deopt.Although a handful of React Native views define their configs in JavaScript (eg
RCTText
,ReactNativeART
) the majority require them to be loaded fromUIManager
(egRCTView
). For consistency/simplicity, all view registrations have been updated to use this new lazy-config approach.Downstream I have also updated
requireNativeComponent
. While testing, I noticed that this method fell back to returning anUnimplementedView
component if a view config could not be found inUIManager
. Apparently this was a short-term solution in place for Ads Manager at a time when Android didn't support many of the RN view types. After discussion with the React Native team, I've added an invariant here instead. This simplifies our Flow typing and avoids unnecessary runtime checks.