@samitouri / QOS-React / commits / 3c980aaf30

Type EventPluginRegistry

This commit takes the first incremental step towards adding type checks to the React event code. A couple of issues came up. There is an issue with the SyntheticEvent type: Flow declares a SyntheticEvent type[1] that lacks the non-public properties which are used in React internals. To solve this I declared a class that extends SyntheticEvent. This class can be expanded as we add Flow types to more places where SyntheticEvent instances are referenced. I'm happy to change this if folks prefer a different approach. Some options I considered: - Override the SyntheticEvent declaration with our own declaration - Pro: We can use 'SyntheticEvent' as a type just like we are used to when working in any other codebase. - Pro: No need to import any type since it's a declaration - Pro: Only one version of SyntheticEvent; less confusion. - Con: Could get out of sync with real implementation. - Con: Duplicates part of the type declared in Flow. - Import the SyntheticEvent class and use that as the type - Pro: Keeps type definition in sync with the real implementation. - Con: Declaration overrides implementation so I'm not sure this would work. - Con: Have to remember to import the type. - Declare a separate type called ReactSyntheticEvent that extends SyntheticEvent - Pro: Stays in sync with the Flow SyntheticEvent type; less duplication. - Pro: Differentiates this type from the Flow SyntheticEvent type; less confusion. - Pro: No need to import any type since it's a declaration - Con: Could get out of sync with real implementation. I also ran into an issue where a variable was only non-null when '__DEV__' is true, similar to PR #7586.[2] The work-around is to force it to be typed non-null and add a comment documenting the reason. At this time Flow doesn't have a better way to deal with the situation. Next steps: - Specific type for the 'dispatchConfig' property of SyntheticEvent - More detailed types for PluginName and PluginModule Lastly; note that I renamed some variables to follow the convention of reserving PascalCase for classes, enums, and Flow types. [1]: https://github.com/facebook/flow/blob/master/lib/react.js#L277-L293 [2]: https://github.com/facebook/react/pull/7586/commits/b99eb5087bc2df3bcb64d76a2d75953978f61f34

Flarnie Marchan committed Aug 31, 2016 at 14:10 UTC 3c980aaf3018b7deed03584a548c3b76cbee4910
2 files changed +93 -41
src/renderers/shared/stack/event/EventPluginRegistry.js
+72 -41
@@ -7,62 +7,78 @@
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 * @providesModule EventPluginRegistry
10 + * @flow
11 */
12
13 'use strict';
14
15 +import type {
16 + DispatchConfig,
17 + ReactSyntheticEvent,
18 +} from 'ReactSyntheticEvent';
19 +
20 +type PluginName = string;
21 +
22 +type PluginModule = {
23 + eventTypes: any,
24 +};
25 +
26 +type NamesToPlugins = {[key: PluginName]: PluginModule};
27 +
28 +type EventPluginOrder = null | Array<PluginName>;
29 +
30 var invariant = require('invariant');
31
32 /**
33 * Injectable ordering of event plugins.
34 */
19 -var EventPluginOrder = null;
35 +var eventPluginOrder: EventPluginOrder = null;
36
37 /**
38 * Injectable mapping from names to event plugin modules.
39 */
24 -var namesToPlugins = {};
40 +var namesToPlugins: NamesToPlugins = {};
41
42 /**
43 * Recomputes the plugin list using the injected plugins and plugin ordering.
44 *
45 * @private
46 */
31 -function recomputePluginOrdering() {
32 - if (!EventPluginOrder) {
33 - // Wait until an `EventPluginOrder` is injected.
47 +function recomputePluginOrdering(): void {
48 + if (!eventPluginOrder) {
49 + // Wait until an `eventPluginOrder` is injected.
50 return;
51 }
52 for (var pluginName in namesToPlugins) {
37 - var PluginModule = namesToPlugins[pluginName];
38 - var pluginIndex = EventPluginOrder.indexOf(pluginName);
53 + var pluginModule = namesToPlugins[pluginName];
54 + var pluginIndex = eventPluginOrder.indexOf(pluginName);
55 invariant(
56 pluginIndex > -1,
57 'EventPluginRegistry: Cannot inject event plugins that do not exist in ' +
58 'the plugin ordering, `%s`.',
43 - pluginName
59 + pluginName,
60 );
61 if (EventPluginRegistry.plugins[pluginIndex]) {
62 continue;
63 }
64 invariant(
49 - PluginModule.extractEvents,
65 + pluginModule.extractEvents,
66 'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
67 'method, but `%s` does not.',
52 - pluginName
68 + pluginName,
69 );
54 - EventPluginRegistry.plugins[pluginIndex] = PluginModule;
55 - var publishedEvents = PluginModule.eventTypes;
70 + EventPluginRegistry.plugins[pluginIndex] = pluginModule;
71 + var publishedEvents = pluginModule.eventTypes;
72 for (var eventName in publishedEvents) {
73 invariant(
74 publishEventForPlugin(
75 publishedEvents[eventName],
60 - PluginModule,
61 - eventName
76 + pluginModule,
77 + eventName,
78 ),
79 'EventPluginRegistry: Failed to publish event `%s` for plugin `%s`.',
80 eventName,
65 - pluginName
81 + pluginName,
82 );
83 }
84 }
@@ -76,12 +92,16 @@ function recomputePluginOrdering() {
92 * @return {boolean} True if the event was successfully published.
93 * @private
94 */
79 -function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
95 +function publishEventForPlugin(
96 + dispatchConfig: DispatchConfig,
97 + pluginModule: PluginModule,
98 + eventName: string,
99 +): boolean {
100 invariant(
101 !EventPluginRegistry.eventNameDispatchConfigs.hasOwnProperty(eventName),
102 'EventPluginHub: More than one plugin attempted to publish the same ' +
103 'event name, `%s`.',
84 - eventName
104 + eventName,
105 );
106 EventPluginRegistry.eventNameDispatchConfigs[eventName] = dispatchConfig;
107
@@ -92,8 +112,8 @@ function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
112 var phasedRegistrationName = phasedRegistrationNames[phaseName];
113 publishRegistrationName(
114 phasedRegistrationName,
95 - PluginModule,
96 - eventName
115 + pluginModule,
116 + eventName,
117 );
118 }
119 }
@@ -101,8 +121,8 @@ function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
121 } else if (dispatchConfig.registrationName) {
122 publishRegistrationName(
123 dispatchConfig.registrationName,
104 - PluginModule,
105 - eventName
124 + pluginModule,
125 + eventName,
126 );
127 return true;
128 }
@@ -117,16 +137,20 @@ function publishEventForPlugin(dispatchConfig, PluginModule, eventName) {
137 * @param {object} PluginModule Plugin publishing the event.
138 * @private
139 */
120 -function publishRegistrationName(registrationName, PluginModule, eventName) {
140 +function publishRegistrationName(
141 + registrationName: string,
142 + pluginModule: PluginModule,
143 + eventName: string,
144 +): void {
145 invariant(
146 !EventPluginRegistry.registrationNameModules[registrationName],
147 'EventPluginHub: More than one plugin attempted to publish the same ' +
148 'registration name, `%s`.',
125 - registrationName
149 + registrationName,
150 );
127 - EventPluginRegistry.registrationNameModules[registrationName] = PluginModule;
151 + EventPluginRegistry.registrationNameModules[registrationName] = pluginModule;
152 EventPluginRegistry.registrationNameDependencies[registrationName] =
129 - PluginModule.eventTypes[eventName].dependencies;
153 + pluginModule.eventTypes[eventName].dependencies;
154
155 if (__DEV__) {
156 var lowerCasedName = registrationName.toLowerCase();
@@ -173,7 +197,8 @@ var EventPluginRegistry = {
197 * only in __DEV__.
198 * @type {Object}
199 */
176 - possibleRegistrationNames: __DEV__ ? {} : null,
200 + possibleRegistrationNames: __DEV__ ? {} : (null: any),
201 + // Trust the developer to only use possibleRegistrationNames in __DEV__
202
203 /**
204 * Injects an ordering of plugins (by plugin name). This allows the ordering
@@ -184,14 +209,16 @@ var EventPluginRegistry = {
209 * @internal
210 * @see {EventPluginHub.injection.injectEventPluginOrder}
211 */
187 - injectEventPluginOrder: function(InjectedEventPluginOrder) {
212 + injectEventPluginOrder: function(
213 + injectedEventPluginOrder: EventPluginOrder,
214 + ): void {
215 invariant(
189 - !EventPluginOrder,
216 + !eventPluginOrder,
217 'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
191 - 'once. You are likely trying to load more than one copy of React.'
218 + 'once. You are likely trying to load more than one copy of React.',
219 );
220 // Clone the ordering so it cannot be dynamically mutated.
194 - EventPluginOrder = Array.prototype.slice.call(InjectedEventPluginOrder);
221 + eventPluginOrder = Array.prototype.slice.call(injectedEventPluginOrder);
222 recomputePluginOrdering();
223 },
224
@@ -205,22 +232,24 @@ var EventPluginRegistry = {
232 * @internal
233 * @see {EventPluginHub.injection.injectEventPluginsByName}
234 */
208 - injectEventPluginsByName: function(injectedNamesToPlugins) {
235 + injectEventPluginsByName: function(
236 + injectedNamesToPlugins: NamesToPlugins
237 + ): void {
238 var isOrderingDirty = false;
239 for (var pluginName in injectedNamesToPlugins) {
240 if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
241 continue;
242 }
214 - var PluginModule = injectedNamesToPlugins[pluginName];
243 + var pluginModule = injectedNamesToPlugins[pluginName];
244 if (!namesToPlugins.hasOwnProperty(pluginName) ||
216 - namesToPlugins[pluginName] !== PluginModule) {
245 + namesToPlugins[pluginName] !== pluginModule) {
246 invariant(
247 !namesToPlugins[pluginName],
248 'EventPluginRegistry: Cannot inject two different event plugins ' +
249 'using the same name, `%s`.',
221 - pluginName
250 + pluginName,
251 );
223 - namesToPlugins[pluginName] = PluginModule;
252 + namesToPlugins[pluginName] = pluginModule;
253 isOrderingDirty = true;
254 }
255 }
@@ -236,7 +265,9 @@ var EventPluginRegistry = {
265 * @return {?object} The plugin that created the supplied event.
266 * @internal
267 */
239 - getPluginModuleForEvent: function(event) {
268 + getPluginModuleForEvent: function(
269 + event: ReactSyntheticEvent,
270 + ): null | PluginModule {
271 var dispatchConfig = event.dispatchConfig;
272 if (dispatchConfig.registrationName) {
273 return EventPluginRegistry.registrationNameModules[
@@ -247,11 +278,11 @@ var EventPluginRegistry = {
278 if (!dispatchConfig.phasedRegistrationNames.hasOwnProperty(phase)) {
279 continue;
280 }
250 - var PluginModule = EventPluginRegistry.registrationNameModules[
281 + var pluginModule = EventPluginRegistry.registrationNameModules[
282 dispatchConfig.phasedRegistrationNames[phase]
283 ];
253 - if (PluginModule) {
254 - return PluginModule;
284 + if (pluginModule) {
285 + return pluginModule;
286 }
287 }
288 return null;
@@ -261,8 +292,8 @@ var EventPluginRegistry = {
292 * Exposed for unit testing.
293 * @private
294 */
264 - _resetEventPlugins: function() {
265 - EventPluginOrder = null;
295 + _resetEventPlugins: function(): void {
296 + eventPluginOrder = null;
297 for (var pluginName in namesToPlugins) {
298 if (namesToPlugins.hasOwnProperty(pluginName)) {
299 delete namesToPlugins[pluginName];
src/renderers/shared/stack/event/ReactSyntheticEvent.js new
+21
@@ -0,0 +1,21 @@
1 +/*
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * Flow type for SyntheticEvent class that includes private properties
10 + *
11 + * @providesModule ReactSyntheticEvent
12 + * @flow
13 + */
14 +
15 +'use strict';
16 +
17 +export type DispatchConfig = any;
18 +
19 +export class ReactSyntheticEvent extends SyntheticEvent {
20 + dispatchConfig: DispatchConfig;
21 +};