Allow UIManager to load native view managers lazily

Summary:
This adds a synchronous method that JS can call to load view managers.
Notably, we don't have an exact way to go from a JS name to the native view manager, so this naively adds 'Manager' to the end.

After lazily loading the view, it makes sure to cache all its values in native and JS, as further calls from JS will fail.

Reviewed By: PeteTheHeat

Differential Revision: D10204314

fbshipit-source-id: ebf42a85dcc467f3b4c5d6e18e49e04f9e8aa4f9
This commit is contained in:
Mehdi Mulani 2018-10-09 14:11:36 -07:00 committed by Facebook Github Bot
parent bbb6a0754c
commit 751be26015
3 changed files with 145 additions and 90 deletions

View File

@ -37,6 +37,7 @@ UIManager.takeSnapshot = function() {
'Use ReactNative.takeSnapshot instead.',
);
};
const triedLoadingConfig = new Set();
UIManager.getViewManagerConfig = function(viewManagerName: string) {
if (
viewManagerConfigs[viewManagerName] === undefined &&
@ -51,9 +52,59 @@ UIManager.getViewManagerConfig = function(viewManagerName: string) {
}
}
const config = viewManagerConfigs[viewManagerName];
if (config) {
return config;
}
if (UIManager.lazilyLoadView && !triedLoadingConfig.has(viewManagerName)) {
const result = UIManager.lazilyLoadView(viewManagerName);
triedLoadingConfig.add(viewManagerName);
if (result.viewConfig) {
UIManager[viewManagerName] = result.viewConfig;
lazifyViewManagerConfig(viewManagerName);
}
}
return viewManagerConfigs[viewManagerName];
};
function lazifyViewManagerConfig(viewName) {
const viewConfig = UIManager[viewName];
if (viewConfig.Manager) {
viewManagerConfigs[viewName] = viewConfig;
defineLazyObjectProperty(viewConfig, 'Constants', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const constants = {};
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value !== 'function') {
constants[key] = value;
}
});
return constants;
},
});
defineLazyObjectProperty(viewConfig, 'Commands', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const commands = {};
let index = 0;
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value === 'function') {
commands[key] = index++;
}
});
return commands;
},
});
}
}
/**
* Copies the ViewManager constants and commands into UIManager. This is
* only needed for iOS, which puts the constants in the ViewManager
@ -61,39 +112,7 @@ UIManager.getViewManagerConfig = function(viewManagerName: string) {
*/
if (Platform.OS === 'ios') {
Object.keys(UIManager).forEach(viewName => {
const viewConfig = UIManager[viewName];
if (viewConfig.Manager) {
viewManagerConfigs[viewName] = viewConfig;
defineLazyObjectProperty(viewConfig, 'Constants', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const constants = {};
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value !== 'function') {
constants[key] = value;
}
});
return constants;
},
});
defineLazyObjectProperty(viewConfig, 'Commands', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const commands = {};
let index = 0;
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value === 'function') {
commands[key] = index++;
}
});
return commands;
},
});
}
lazifyViewManagerConfig(viewName);
});
} else if (UIManager.ViewManagerNames) {
// We want to add all the view managers to the UIManager.

View File

@ -64,4 +64,5 @@ module.exports = [
'focus',
'genericBubblingEventTypes',
'genericDirectEventTypes',
'lazilyLoadView',
];

View File

@ -68,7 +68,7 @@ NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotif
NSHashTable<RCTShadowView *> *_shadowViewsWithUpdatedChildren; // UIManager queue only.
// Keyed by viewName
NSDictionary *_componentDataByName;
NSMutableDictionary *_componentDataByName;
}
@synthesize bridge = _bridge;
@ -148,18 +148,16 @@ RCT_EXPORT_MODULE()
_observerCoordinator = [RCTUIManagerObserverCoordinator new];
// Get view managers from bridge
NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
// Get view managers from bridge=
_componentDataByName = [NSMutableDictionary new];
for (Class moduleClass in _bridge.moduleClasses) {
if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass
bridge:_bridge];
componentDataByName[componentData.name] = componentData;
_componentDataByName[componentData.name] = componentData;
}
}
_componentDataByName = [componentDataByName copy];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveNewContentSizeMultiplier)
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
@ -1485,6 +1483,62 @@ RCT_EXPORT_METHOD(clearJSResponder)
}];
}
static NSMutableDictionary<NSString *, id> *moduleConstantsForComponent(
NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
RCTComponentData *componentData) {
NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
// Register which event-types this view dispatches.
// React needs this for the event plugin.
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];
// Add manager class
moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
// Add native props
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
moduleConstants[@"directEventTypes"] = directEventTypes;
// Add direct events
for (NSString *eventName in viewConfig[@"directEvents"]) {
if (!directEvents[eventName]) {
directEvents[eventName] = @{
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
directEventTypes[eventName] = directEvents[eventName];
if (RCT_DEBUG && bubblingEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a "
"direct event", componentData.name, eventName);
}
}
// Add bubbling events
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
if (!bubblingEvents[eventName]) {
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
bubblingEvents[eventName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
bubblingEventTypes[eventName] = bubblingEvents[eventName];
if (RCT_DEBUG && directEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered direct event '%@' as a "
"bubbling event", componentData.name, eventName);
}
}
return moduleConstants;
}
- (NSDictionary<NSString *, id> *)constantsToExport
{
NSMutableDictionary<NSString *, NSDictionary *> *constants = [NSMutableDictionary new];
@ -1492,62 +1546,43 @@ RCT_EXPORT_METHOD(clearJSResponder)
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
NSMutableDictionary<NSString *, id> *moduleConstants = [NSMutableDictionary new];
// Register which event-types this view dispatches.
// React needs this for the event plugin.
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEventTypes = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSDictionary *> *directEventTypes = [NSMutableDictionary new];
// Add manager class
moduleConstants[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
// Add native props
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
moduleConstants[@"bubblingEventTypes"] = bubblingEventTypes;
moduleConstants[@"directEventTypes"] = directEventTypes;
// Add direct events
for (NSString *eventName in viewConfig[@"directEvents"]) {
if (!directEvents[eventName]) {
directEvents[eventName] = @{
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
directEventTypes[eventName] = directEvents[eventName];
if (RCT_DEBUG && bubblingEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a "
"direct event", componentData.name, eventName);
}
}
// Add bubbling events
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
if (!bubblingEvents[eventName]) {
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
bubblingEvents[eventName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
bubblingEventTypes[eventName] = bubblingEvents[eventName];
if (RCT_DEBUG && directEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered direct event '%@' as a "
"bubbling event", componentData.name, eventName);
}
}
RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name);
constants[name] = moduleConstants;
RCTAssert(!constants[name], @"UIManager already has constants for %@", componentData.name);
NSMutableDictionary<NSString *, id> *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
constants[name] = moduleConstants;
}];
return constants;
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(lazilyLoadView:(NSString *)name)
{
if (_componentDataByName[name]) {
return @{};
}
id<RCTBridgeDelegate> delegate = self.bridge.delegate;
if (![delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) {
return @{};
}
NSString *moduleName = [name stringByAppendingString:@"Manager"];
BOOL result = [delegate bridge:self.bridge didNotFindModule:moduleName];
if (!result) {
return @{};
}
id module = [self.bridge moduleForName:moduleName];
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:[module class] bridge:self.bridge];
_componentDataByName[componentData.name] = componentData;
NSMutableDictionary *directEvents = [NSMutableDictionary new];
NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
NSMutableDictionary<NSString *, id> *moduleConstants = moduleConstantsForComponent(directEvents, bubblingEvents, componentData);
return
@{
@"viewConfig": moduleConstants,
};
}
RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config
withCallback:(RCTResponseSenderBlock)callback
errorCallback:(__unused RCTResponseSenderBlock)errorCallback)