-
Notifications
You must be signed in to change notification settings - Fork 47.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[react-interactions] Add experimental ReactDOM Listener API #17508
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit e33346d:
|
Details of bundled changes.Comparing: acfe4b2...e33346d react-noop-renderer
react-dom
react-reconciler
react-art
react-test-renderer
react-native-renderer
ReactDOM: size: 🔺+0.1%, gzip: -0.0% Size changes (stable) |
Details of bundled changes.Comparing: acfe4b2...e33346d react-dom
react-test-renderer
react-native-renderer
react-noop-renderer
react-art
react-reconciler
ReactDOM: size: 0.0%, gzip: 0.0% Size changes (experimental) |
08ab510
to
6d20886
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to note: I've come to really appreciated the documentation and ascii diagrams contained in the ResponderEventPlugin. It's really helpful to get an overview of how a system is designed before reading through the code.
react/packages/legacy-events/ResponderEventPlugin.js
Lines 205 to 282 in ab1a4f2
/* Negotiation Performed | |
+-----------------------+ | |
/ \ | |
Process low level events to + Current Responder + wantsResponderID | |
determine who to perform negot-| (if any exists at all) | | |
iation/transition | Otherwise just pass through| | |
-------------------------------+----------------------------+------------------+ | |
Bubble to find first ID | | | |
to return true:wantsResponderID| | | |
| | | |
+-------------+ | | | |
| onTouchStart| | | | |
+------+------+ none | | | |
| return| | | |
+-----------v-------------+true| +------------------------+ | | |
|onStartShouldSetResponder|----->|onResponderStart (cur) |<-----------+ | |
+-----------+-------------+ | +------------------------+ | | | |
| | | +--------+-------+ | |
| returned true for| false:REJECT +-------->|onResponderReject | |
| wantsResponderID | | | +----------------+ | |
| (now attempt | +------------------+-----+ | | |
| handoff) | | onResponder | | | |
+------------------->| TerminationRequest| | | |
| +------------------+-----+ | | |
| | | +----------------+ | |
| true:GRANT +-------->|onResponderGrant| | |
| | +--------+-------+ | |
| +------------------------+ | | | |
| | onResponderTerminate |<-----------+ | |
| +------------------+-----+ | | |
| | | +----------------+ | |
| +-------->|onResponderStart| | |
| | +----------------+ | |
Bubble to find first ID | | | |
to return true:wantsResponderID| | | |
| | | |
+-------------+ | | | |
| onTouchMove | | | | |
+------+------+ none | | | |
| return| | | |
+-----------v-------------+true| +------------------------+ | | |
|onMoveShouldSetResponder |----->|onResponderMove (cur) |<-----------+ | |
+-----------+-------------+ | +------------------------+ | | | |
| | | +--------+-------+ | |
| returned true for| false:REJECT +-------->|onResponderRejec| | |
| wantsResponderID | | | +----------------+ | |
| (now attempt | +------------------+-----+ | | |
| handoff) | | onResponder | | | |
+------------------->| TerminationRequest| | | |
| +------------------+-----+ | | |
| | | +----------------+ | |
| true:GRANT +-------->|onResponderGrant| | |
| | +--------+-------+ | |
| +------------------------+ | | | |
| | onResponderTerminate |<-----------+ | |
| +------------------+-----+ | | |
| | | +----------------+ | |
| +-------->|onResponderMove | | |
| | +----------------+ | |
| | | |
| | | |
Some active touch started| | | |
inside current responder | +------------------------+ | | |
+------------------------->| onResponderEnd | | | |
| | +------------------------+ | | |
+---+---------+ | | | |
| onTouchEnd | | | | |
+---+---------+ | | | |
| | +------------------------+ | | |
+------------------------->| onResponderEnd | | | |
No active touches started| +-----------+------------+ | | |
inside current responder | | | | |
| v | | |
| +------------------------+ | | |
| | onResponderRelease | | | |
| +------------------------+ | | |
| | | |
+ + */ |
packages/react-dom/src/events/__tests__/DOMEventListenerSystem-test-internal.js
Outdated
Show resolved
Hide resolved
packages/react-dom/src/events/__tests__/DOMEventListenerSystem-test-internal.js
Outdated
Show resolved
Hide resolved
packages/react-dom/src/events/__tests__/DOMEventListenerSystem-test-internal.js
Outdated
Show resolved
Hide resolved
packages/react-dom/src/events/__tests__/DOMEventListenerSystem-test-internal.js
Outdated
Show resolved
Hide resolved
f273405
to
12098ff
Compare
12098ff
to
931d40e
Compare
More features More features More features More features More features add file add file WIP WIP FIX WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP WIP Cleanup Cleanup Address feedback and refactor some logic Address feedback Fix conflict Fix renderers Remove comment Fix custom renderer Fix Revert flag Revert flag Propagation clean up Propagation clean up Used named import
931d40e
to
e33346d
Compare
This is really cool. Forgive me if this is super obvious, but is there a going to be a way to dispatch custom events? Use case would be sharing a listener with complex inputs (like react-select, draftjs, etc.) const inputLogger = unstable_createListener('change', (eventOrValue) => {
if (isEvent(eventOrValue) {
console.log(eventOrValue.target.value)
} else {
console.log(value)
}
} |
@jaredpalmer I'm a bit confused by your example? This API binds around the native browser event, so you'd always get back the native browser event. You can capture custom native events too, but you'd have to dispatch those via the DOM for the event system to pick it up. If you wanted something like your example above, you'd likely have to add another layer on top of the Listener API rather than consuming it directly; then you can pass around any value you want. |
@trueadm got it. that makes sense. |
I'm closing this PR. We might want to re-visit this approach again in the future, but there are so many conflicts that it's probably better to start again (plus there are fundamental flaws in some aspects of this PR's design). For a look an alternative approach we're thinking of, see: #17651. |
This PR adds the experimental Listener API to React behind a flag. Note: For now this is only intended to be used internally at Facebook, and we have no initial plans to ship this to open source.
The listener API includes its own React event system that leverages the lessons learned from the existing event system and that of the experimental React Flare event system. There are three new APIs that are added to
react-dom
:ReactDOM.unstable_createListener
ReactDOM.unstable_createRootListener
ReactDOM.unstable_addEventPriority
The Listener API works in a similar way to the DOM's
addEventListener
API, except this API returns a special React Listener object that can be supplied to elements via the internallisteners
prop. An example of this follows:Listener Options
Both the
createListener
andcreateRootListener
APIs support an optional third argument for specifying listener options. There are two boolean options that can be specified,capture
andpassive
. Here's an example to demonstrate this:Root Listeners
Sometimes, it's ideal to attach listeners to the root document of an app, so events can be captured that don't hit a specific target element. For these cases, the
createRootListener
API can be used:Combining multiple listeners
Elements support having multiple listeners, even of the same type on an element, to do this, arrays can be used (and nested) to supply many listeners. Listeners of the same type will execute in array order of how they're supplied to the element:
Event Object
Unlike the existing React event system, the Listener API supplies only the native object to event callbacks – there are no Synthetic Events. The only caveat is that the native objects get monkey-patched by React, with several of the properties and methods on the event object replaced with alternatives that interface with React's event delegation and propagation system. Calling
stopPropagation
on the monkey-patched event object will not trigger the nativestopPropagation
method (unlike the existing React event system, which does), but rather prevent propagation through React's propagation system only.Event Flushing
With the Listener API, we don't flush on the native event triggering unless we have React Listeners that are actually triggered, this should help reduce needlessly flushing work when the event has no actual action.
This PR depends on #17513