Support native ViewManager inheritance on iOS

Summary:
**Motivation**
This is a re-worked version of #14260, by shergin's suggestion.

For iOS, if you want to inherit from a native ViewManagers, your custom ViewManager will not automatically export the parents' props. So the only way to do this today, is to basically copy/paste the parent ViewManager-file, and add your own custom logic.

With this PR, this is made more extensible by exporting the `baseModuleName` (i.e. the iOS `superclass` of the ViewManager), and then using that value to re-establish the inheritance relationship in `requireNativeComponent`.

**Test plan**
I've run this with a test project, and it works fine there. But needs more testing.

Opened this PR as [per shergin's suggestion](https://github.com/facebook/react-native/pull/10946#issuecomment-311860545) though, so we can discuss approach.

**Discussion**
* Android already supports inheritance, so this change should be compatible with that. But, not every prop available on `UIManager.RCTView.NativeProps` is actually exported by every ViewManager. So should `UIManager.RCTView.NativeProps` still be merged with `viewConfig.NativeProps`, even if the individual ViewManager does not export/use them to begin with?
* Does this break other platforms? [UWP](https://github.com/Microsoft/react-native-windows)?
Closes https://github.com/facebook/react-native/pull/14775

Differential Revision: D5392953

Pulled By: shergin

fbshipit-source-id: 5212da616acfba50cc285e2997d183cf8b2cd09f
This commit is contained in:
Christian Brevik 2017-07-10 15:48:34 -07:00 committed by Facebook Github Bot
parent 10230707cb
commit 684e03590b
3 changed files with 41 additions and 25 deletions

View File

@ -70,13 +70,19 @@ function requireNativeComponent(
viewConfig.propTypes = null;
}
// The ViewConfig doesn't contain any props inherited from the view manager's
// superclass, so we manually merge in the RCTView ones. Other inheritance
// patterns are currenty not supported.
const nativeProps = {
...UIManager.RCTView.NativeProps,
...viewConfig.NativeProps,
};
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 = {};

View File

@ -1396,6 +1396,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
// Add native props
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
// Add direct events
for (NSString *eventName in viewConfig[@"directEvents"]) {

View File

@ -41,23 +41,7 @@ typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
_viewPropBlocks = [NSMutableDictionary new];
_shadowPropBlocks = [NSMutableDictionary new];
// Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
// We want to get rid of RCT and RK prefixes, but a lot of JS code still references
// view names by prefix. So, while RCTBridgeModuleNameForClass now drops these
// prefixes by default, we'll still keep them around here.
NSString *name = [managerClass moduleName];
if (name.length == 0) {
name = NSStringFromClass(managerClass);
}
if ([name hasPrefix:@"RK"]) {
name = [name stringByReplacingCharactersInRange:(NSRange){0, @"RK".length} withString:@"RCT"];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
RCTAssert(name.length, @"Invalid moduleName '%@'", name);
_name = name;
_name = moduleNameForClass(managerClass);
_implementsUIBlockToAmendWithShadowViewRegistry = NO;
Class cls = _managerClass;
@ -439,11 +423,14 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S
}
}
#endif
Class superClass = [_managerClass superclass];
return @{
@"propTypes": propTypes,
@"directEvents": directEvents,
@"bubblingEvents": bubblingEvents,
@"baseModuleName": superClass == [NSObject class] ? [NSNull null] : moduleNameForClass(superClass)
};
}
@ -455,4 +442,26 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S
return nil;
}
static NSString *moduleNameForClass(Class managerClass)
{
// Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
// We want to get rid of RCT and RK prefixes, but a lot of JS code still references
// view names by prefix. So, while RCTBridgeModuleNameForClass now drops these
// prefixes by default, we'll still keep them around here.
NSString *name = [managerClass moduleName];
if (name.length == 0) {
name = NSStringFromClass(managerClass);
}
if ([name hasPrefix:@"RK"]) {
name = [name stringByReplacingCharactersInRange:(NSRange){0, @"RK".length} withString:@"RCT"];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
RCTAssert(name.length, @"Invalid moduleName '%@'", name);
return name;
}
@end