Skip to content

Commit 8488398

Browse files
committed
Added mechanism for directly mapping JS event handlers to blocks
Summary: Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher. This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names. The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g. RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock) If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
1 parent 4379aa0 commit 8488398

46 files changed

Lines changed: 552 additions & 512 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Libraries/Components/MapView/MapView.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ if (Platform.OS === 'android') {
277277
uiViewClassName: 'RCTMap',
278278
});
279279
} else {
280-
var RCTMap = requireNativeComponent('RCTMap', MapView);
280+
var RCTMap = requireNativeComponent('RCTMap', MapView, {
281+
nativeOnly: {onChange: true, onPress: true}
282+
});
281283
}
282284

283285
module.exports = MapView;

Libraries/Components/SliderIOS/SliderIOS.ios.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ var styles = StyleSheet.create({
107107
},
108108
});
109109

110-
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
110+
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
111+
nativeOnly: { onChange: true },
112+
});
111113

112114
module.exports = SliderIOS;

Libraries/Components/SwitchIOS/SwitchIOS.ios.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ var styles = StyleSheet.create({
108108
},
109109
});
110110

111-
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
111+
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
112+
nativeOnly: { onChange: true }
113+
});
112114

113115
module.exports = SwitchIOS;

Libraries/Components/WebView/WebView.ios.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,13 @@ var WebView = React.createClass({
226226
},
227227
});
228228

229-
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
229+
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
230+
nativeOnly: {
231+
onLoadingStart: true,
232+
onLoadingError: true,
233+
onLoadingFinish: true,
234+
},
235+
});
230236

231237
var styles = StyleSheet.create({
232238
container: {

Libraries/Image/RCTImageView.m

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222
@interface RCTImageView ()
2323

24-
@property (nonatomic, assign) BOOL onLoadStart;
25-
@property (nonatomic, assign) BOOL onProgress;
26-
@property (nonatomic, assign) BOOL onError;
27-
@property (nonatomic, assign) BOOL onLoad;
28-
@property (nonatomic, assign) BOOL onLoadEnd;
24+
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
25+
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
26+
@property (nonatomic, copy) RCTDirectEventBlock onError;
27+
@property (nonatomic, copy) RCTDirectEventBlock onLoad;
28+
@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd;
2929

3030
@end
3131

@@ -116,19 +116,16 @@ - (void)reloadImage
116116
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
117117

118118
if (_onLoadStart) {
119-
NSDictionary *event = @{ @"target": self.reactTag };
120-
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
119+
_onLoadStart(nil);
121120
}
122121

123122
RCTImageLoaderProgressBlock progressHandler = nil;
124123
if (_onProgress) {
125124
progressHandler = ^(int64_t loaded, int64_t total) {
126-
NSDictionary *event = @{
127-
@"target": self.reactTag,
125+
_onProgress(@{
128126
@"loaded": @((double)loaded),
129127
@"total": @((double)total),
130-
};
131-
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
128+
});
132129
};
133130
}
134131

@@ -147,21 +144,15 @@ - (void)reloadImage
147144
}
148145
if (error) {
149146
if (_onError) {
150-
NSDictionary *event = @{
151-
@"target": self.reactTag,
152-
@"error": error.localizedDescription,
153-
};
154-
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
147+
_onError(@{ @"error": error.localizedDescription });
155148
}
156149
} else {
157150
if (_onLoad) {
158-
NSDictionary *event = @{ @"target": self.reactTag };
159-
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
151+
_onLoad(nil);
160152
}
161153
}
162154
if (_onLoadEnd) {
163-
NSDictionary *event = @{ @"target": self.reactTag };
164-
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
155+
_onLoadEnd(nil);
165156
}
166157
}];
167158
} else {

Libraries/Image/RCTImageViewManager.m

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ - (UIView *)view
2727
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
2828
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
2929
RCT_EXPORT_VIEW_PROPERTY(src, NSString)
30-
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, BOOL)
31-
RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL)
32-
RCT_EXPORT_VIEW_PROPERTY(onError, BOOL)
33-
RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL)
34-
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, BOOL)
30+
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
31+
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
32+
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
33+
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
34+
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
3535
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
3636
{
3737
if (json) {
@@ -43,15 +43,4 @@ - (UIView *)view
4343
}
4444
}
4545

46-
- (NSArray *)customDirectEventTypes
47-
{
48-
return @[
49-
@"loadStart",
50-
@"progress",
51-
@"error",
52-
@"load",
53-
@"loadEnd",
54-
];
55-
}
56-
5746
@end

React/Modules/RCTUIManager.m

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,9 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
455455
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
456456
[areNew addObject:@(shadowView.isNewView)];
457457
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
458+
459+
// TODO (#8214142): this can be greatly simplified by sending the layout
460+
// event directly from the shadow thread, which may be better anyway.
458461
id event = (id)kCFNull;
459462
if (shadowView.onLayout) {
460463
event = @{
@@ -1128,67 +1131,68 @@ static void RCTMeasureLayout(RCTShadowView *view,
11281131
}];
11291132
}
11301133

1131-
- (NSDictionary *)bubblingEventsConfig
1134+
- (NSDictionary *)constantsToExport
11321135
{
1133-
NSMutableDictionary *customBubblingEventTypesConfigs = [NSMutableDictionary new];
1134-
for (RCTComponentData *componentData in _componentDataByName.allValues) {
1135-
RCTViewManager *manager = componentData.manager;
1136-
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
1137-
NSArray *events = [manager customBubblingEventTypes];
1138-
if (RCT_DEBUG) {
1139-
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
1140-
@"customBubblingEventTypes must return an array, but %@ returned %@",
1141-
[manager class], [events class]);
1142-
}
1143-
for (NSString *eventName in events) {
1144-
NSString *topName = RCTNormalizeInputEventName(eventName);
1145-
if (!customBubblingEventTypesConfigs[topName]) {
1146-
NSString *bubbleName = [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
1147-
customBubblingEventTypesConfigs[topName] = @{
1148-
@"phasedRegistrationNames": @{
1149-
@"bubbled": bubbleName,
1150-
@"captured": [bubbleName stringByAppendingString:@"Capture"],
1151-
}
1152-
};
1153-
}
1154-
}
1155-
}
1156-
};
1136+
NSMutableDictionary *allJSConstants = [NSMutableDictionary new];
1137+
NSMutableDictionary *directEvents = [NSMutableDictionary new];
1138+
NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
11571139

1158-
return customBubblingEventTypesConfigs;
1159-
}
1140+
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
1141+
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
11601142

1161-
- (NSDictionary *)directEventsConfig
1162-
{
1163-
NSMutableDictionary *customDirectEventTypes = [NSMutableDictionary new];
1164-
for (RCTComponentData *componentData in _componentDataByName.allValues) {
1165-
RCTViewManager *manager = componentData.manager;
1166-
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
1167-
NSArray *events = [manager customDirectEventTypes];
1168-
if (RCT_DEBUG) {
1169-
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
1170-
@"customDirectEventTypes must return an array, but %@ returned %@",
1171-
[manager class], [events class]);
1172-
}
1173-
for (NSString *eventName in events) {
1174-
NSString *topName = RCTNormalizeInputEventName(eventName);
1175-
if (!customDirectEventTypes[topName]) {
1176-
customDirectEventTypes[topName] = @{
1177-
@"registrationName": [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
1178-
};
1179-
}
1180-
}
1181-
}
1182-
};
1143+
RCTViewManager *manager = componentData.manager;
1144+
NSMutableDictionary *constantsNamespace =
1145+
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
11831146

1184-
return customDirectEventTypes;
1185-
}
1147+
// Add custom constants
1148+
// TODO: should these be inherited?
1149+
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
1150+
if (constants.count) {
1151+
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
1152+
// add an additional 'Constants' namespace for each class
1153+
constantsNamespace[@"Constants"] = constants;
1154+
}
1155+
1156+
// Add native props
1157+
NSDictionary *viewConfig = [componentData viewConfig];
1158+
constantsNamespace[@"NativeProps"] = viewConfig[@"propTypes"];
1159+
1160+
// Add direct events
1161+
for (NSString *eventName in viewConfig[@"directEvents"]) {
1162+
if (!directEvents[eventName]) {
1163+
directEvents[eventName] = @{
1164+
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
1165+
};
1166+
}
1167+
if (RCT_DEBUG && bubblingEvents[eventName]) {
1168+
RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a "
1169+
"direct event", componentData.name, eventName);
1170+
}
1171+
}
1172+
1173+
// Add bubbling events
1174+
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
1175+
if (!bubblingEvents[eventName]) {
1176+
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
1177+
bubblingEvents[eventName] = @{
1178+
@"phasedRegistrationNames": @{
1179+
@"bubbled": bubbleName,
1180+
@"captured": [bubbleName stringByAppendingString:@"Capture"],
1181+
}
1182+
};
1183+
}
1184+
if (RCT_DEBUG && directEvents[eventName]) {
1185+
RCTLogError(@"Component '%@' re-registered direct event '%@' as a "
1186+
"bubbling event", componentData.name, eventName);
1187+
}
1188+
}
1189+
1190+
allJSConstants[name] = [constantsNamespace copy];
1191+
}];
11861192

1187-
- (NSDictionary *)constantsToExport
1188-
{
1189-
NSMutableDictionary *allJSConstants = [@{
1190-
@"customBubblingEventTypes": [self bubblingEventsConfig],
1191-
@"customDirectEventTypes": [self directEventsConfig],
1193+
[allJSConstants addEntriesFromDictionary:@{
1194+
@"customBubblingEventTypes": bubblingEvents,
1195+
@"customDirectEventTypes": directEvents,
11921196
@"Dimensions": @{
11931197
@"window": @{
11941198
@"width": @(RCTScreenSize().width),
@@ -1200,28 +1204,8 @@ - (NSDictionary *)constantsToExport
12001204
@"height": @(RCTScreenSize().height),
12011205
},
12021206
},
1203-
} mutableCopy];
1204-
1205-
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
1206-
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
1207-
RCTViewManager *manager = componentData.manager;
1208-
NSMutableDictionary *constantsNamespace =
1209-
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
1210-
1211-
// Add custom constants
1212-
// TODO: should these be inherited?
1213-
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
1214-
if (constants.count) {
1215-
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
1216-
// add an additional 'Constants' namespace for each class
1217-
constantsNamespace[@"Constants"] = constants;
1218-
}
1219-
1220-
// Add native props
1221-
constantsNamespace[@"NativeProps"] = [componentData viewConfig];
1222-
1223-
allJSConstants[name] = [constantsNamespace copy];
12241207
}];
1208+
12251209
return allJSConstants;
12261210
}
12271211

React/React.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
1111
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; };
1212
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; };
13+
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; };
1314
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
1415
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
1516
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
@@ -104,6 +105,8 @@
104105
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = "<group>"; };
105106
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = "<group>"; };
106107
131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControlManager.m; sourceTree = "<group>"; };
108+
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePicker.h; sourceTree = "<group>"; };
109+
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePicker.m; sourceTree = "<group>"; };
107110
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
108111
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
109112
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
@@ -345,6 +348,8 @@
345348
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
346349
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
347350
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
351+
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */,
352+
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */,
348353
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
349354
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
350355
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
@@ -597,6 +602,7 @@
597602
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */,
598603
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
599604
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
605+
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */,
600606
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
601607
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
602608
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,

React/Views/RCTComponent.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
#import <CoreGraphics/CoreGraphics.h>
1111

12+
/**
13+
* These block types can be used for mapping input event handlers from JS to view
14+
* properties. Unlike JS method callbacks, these can be called multiple times.
15+
*/
16+
typedef void (^RCTDirectEventBlock)(NSDictionary *body);
17+
typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
18+
1219
/**
1320
* Logical node in a tree of application components. Both `ShadowView` and
1421
* `UIView` conforms to this. Allows us to write utilities that reason about

0 commit comments

Comments
 (0)