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;
|
viewConfig.propTypes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ViewConfig doesn't contain any props inherited from the view manager's
|
let baseModuleName = viewConfig.baseModuleName;
|
||||||
// superclass, so we manually merge in the RCTView ones. Other inheritance
|
let nativeProps = { ...viewConfig.NativeProps };
|
||||||
// patterns are currenty not supported.
|
while (baseModuleName) {
|
||||||
const nativeProps = {
|
const baseModule = UIManager[baseModuleName];
|
||||||
...UIManager.RCTView.NativeProps,
|
if (!baseModule) {
|
||||||
...viewConfig.NativeProps,
|
warning(false, 'Base module "%s" does not exist', baseModuleName);
|
||||||
};
|
baseModuleName = null;
|
||||||
|
} else {
|
||||||
|
nativeProps = { ...nativeProps, ...baseModule.NativeProps };
|
||||||
|
baseModuleName = baseModule.baseModuleName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const key in nativeProps) {
|
for (const key in nativeProps) {
|
||||||
let useAttribute = false;
|
let useAttribute = false;
|
||||||
const attribute = {};
|
const attribute = {};
|
||||||
|
|
|
@ -1396,6 +1396,7 @@ RCT_EXPORT_METHOD(clearJSResponder)
|
||||||
// Add native props
|
// Add native props
|
||||||
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
|
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
|
||||||
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
|
moduleConstants[@"NativeProps"] = viewConfig[@"propTypes"];
|
||||||
|
moduleConstants[@"baseModuleName"] = viewConfig[@"baseModuleName"];
|
||||||
|
|
||||||
// Add direct events
|
// Add direct events
|
||||||
for (NSString *eventName in viewConfig[@"directEvents"]) {
|
for (NSString *eventName in viewConfig[@"directEvents"]) {
|
||||||
|
|
|
@ -41,23 +41,7 @@ typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
|
||||||
_viewPropBlocks = [NSMutableDictionary new];
|
_viewPropBlocks = [NSMutableDictionary new];
|
||||||
_shadowPropBlocks = [NSMutableDictionary new];
|
_shadowPropBlocks = [NSMutableDictionary new];
|
||||||
|
|
||||||
// Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
|
_name = moduleNameForClass(managerClass);
|
||||||
// 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;
|
|
||||||
|
|
||||||
_implementsUIBlockToAmendWithShadowViewRegistry = NO;
|
_implementsUIBlockToAmendWithShadowViewRegistry = NO;
|
||||||
Class cls = _managerClass;
|
Class cls = _managerClass;
|
||||||
|
@ -439,11 +423,14 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Class superClass = [_managerClass superclass];
|
||||||
|
|
||||||
return @{
|
return @{
|
||||||
@"propTypes": propTypes,
|
@"propTypes": propTypes,
|
||||||
@"directEvents": directEvents,
|
@"directEvents": directEvents,
|
||||||
@"bubblingEvents": bubblingEvents,
|
@"bubblingEvents": bubblingEvents,
|
||||||
|
@"baseModuleName": superClass == [NSObject class] ? [NSNull null] : moduleNameForClass(superClass)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,4 +442,26 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S
|
||||||
return nil;
|
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
|
@end
|
||||||
|
|
Loading…
Reference in New Issue