- React and React Native
- Adam Boduch
- 962字
- 2021-07-09 19:28:53
Event handler context and parameters
In this section, we'll examine React components that automatically bind their event handler contexts and how you can pass data into event handlers. Having the right context is important for React event handler functions, because they usually need access to properties or state of the component. Being able to parameterize event handlers is also important, because they don't pull data out of DOM elements.
Auto-binding context
The components you've implemented so far in this book have used the ES2015 class style declaration. This is where you declare a class that extends the base React Component
class. When you do this, however, any event handler methods in the component will need to be manually bound to the component context. For example, if you need access to this.props
, this
needs to be a reference to the component.
You can use the React.createClass()
function to declare a component and have its method contexts auto bind to the component. In other words, there's no need to call bind()
on your callback functions. Let's see an example of this in action:
import React from 'react'; export default React.createClass({ // This event handler requires access to the // component properties, but it doesn't need // to explicitly bind it's context, because // "createClass()" components do this automatically. onClick() { console.log('clicked', `"${this.props.children}"`); }, // Renders a button with a bound event handler. render() { return ( <button onClick={this.onClick}> {this.props.children} </button> ); }, });
This looks a lot like a class declaration. In fact, this used to be the only supported approach for declaring React components. This component in particular handles a button click event. The onClick()
method needs access to the component because it references this.props.children
.
So, you might want to use this function to declare your components if you have a lot of code that manually binds the context of your event handler functions.
Note
You can leverage ES2015 syntax to have event handler methods auto bind their context in component class declarations. We'll introduce this approach a little later on in the book.
Getting component data
In the preceding example, the event handler needed access to the component so that it could read the children
property. In this section, we'll look at a more involved scenario where the handler needs access to component properties, as well as argument values.
We'll render a custom list component that has a click event handler for each item in the list. We'll pass the component a collection as follows:
import React from 'react'; import { render } from 'react-dom'; import MyList from './MyList'; // The items to pass to "<MyList>" as a property. const items = [ { id: 0, name: 'First' }, { id: 1, name: 'Second' }, { id: 2, name: 'Third' }, ]; // Renders "<MyList>" with an "items" property. render( (<MyList items={items} />), document.getElementById('app') );
As you can see, each item in the list has an id
property, used to identify the item. We'll need to be able to access this ID when the item is clicked in the UI so that the event handler can work with the item. Here's what the MyList
component implementation looks like:
import React, { Component } from 'react'; export default class MyList extends Component { constructor() { super(); // We want to make sure that the "onClick()" // handler is explicitly bound to this component // as it's context. this.onClick = this.onClick.bind(this); } // When a list item is clicked, look up the name // of the item based on the "id" argument. This is // why we need access to the component through "this", // for the properties. onClick(id) { const { name } = this.props.items.find( i => i.id === id ); console.log('clicked', `"${name}"`); } render() { return ( <ul> {/* Creates a new handler function with the bound "id" argument. Notice that the context is left as null, since that has already been bound in the constructor. */} {this.props.items.map(({ id, name }) => ( <li key={id} onClick={this.onClick.bind(null, id)} > {name} </li> ))} </ul> ); } }
Here is what the rendered list looks like:
We have to take care of binding the event handler context, which is done in the constructor. If you look at the onClick()
event handler, you can see that it needs access to the component so that it can look up the clicked item in this.props.items
. Also, the onClick()
handler is expecting an id
parameter. If you take a look at the JSX content of this component, you can see that we're calling bind()
to supply the argument value for each item in the list. This means that when the handler is called in response to a click event, the id
of the item is already provided.
This approach to parameterized event handling is quite different from prior approaches. For example, I used to rely on getting parameter data from the DOM element itself. This works well in that we only need one event handler, and it can extract the data it needs from the event argument. This approach also doesn't require setting up several new functions by iterating over a collection and calling bind()
.
And therein lies the trade-off. React applications avoid touching the DOM, because the DOM is really just a render target for React components. If we can write code that doesn't introduce explicit dependencies to DOM elements, the code will be very portable. This is what we've done with the event handler in this example.
Note
If you're concerned about the performance implications of creating a new function for every item in a collection, don't be. You're not going to render thousands of items on the page at a time. Benchmark your code, and if it turns out that bind()
calls on your React event handlers are the slowest part, then you probably have a really fast application.