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:
parent
10230707cb
commit
684e03590b
|
@ -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 = {};
|
||||
|
|
|
@ -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"]) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue