Reputation: 813
So I am trying to learn JavaScript
and/or react
and got a little mixed up with understanding .bind(this)
in the constructor. However, I think to understand it now, and just want to know,
Why would anyone use Binding vs an Arrow-function in JavaScript
?
(or in the onClick
event).
See below code samples.
Binding method ensures this
in clickEvent
function references the class:
class Click extends react.Component {
constructor(props) {
super(props)
this.clickEvent = this.clickEvent.bind(this);
}
render = () => (
<button onClick= { this.clickEvent } > Click Me < /button>
)
clickEvent() { console.log(this) } // 'this' refers to the class
}
However below method also references the class:
class Click extends react.Component {
render = () => (
<button onClick= {() => { this.clickEvent() }}> Click Me < /button>
)
clickEvent() { console.log(this) } // 'this' refers to the class
}
Upvotes: 60
Views: 41263
Reputation: 8745
First of all, let's start with examples of each technique!
But the difference is more related to JavaScript
language itself.
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler() {
console.log( this )
}
render() {
return <button onClick={this.clickHandler}>Click Me</button>
}
}
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props)
}
clickHandler = () => {
console.log( this )
}
render() {
return <button onClick={this.clickHandler}>Click Me</button>
}
}
Using the Arrow-function on public-class-field is more human-readable, because of fewer lines of code, But keep in mind that using Arrow-function can affect three things:
First the memory and performance; When you use a class field to define a function, your whole method resides on each instance of the class and NOT on the prototype, but using the bind technic, just a small callback
is stored on each instance, which calls your method that is stored on the prototype.
Second thing that can be affected is how you write your unit tests. You won't be able to use the component prototype to stub on function calls like below:
const spy = jest.spyOn(MyComponent.prototype, 'clickHandler');
// ...
expect(spy).toHaveBeenCalled();
One would have to find another way to stub the method, for example, either by passing a dummy-callback in props
, or checking the state
changes.
Lastly the performance;
depending on the JavaScript
engine that your script runs on,
using more memory than a certain limit may cause almost random run-time hangs
(aka delay in logic's operation),
and that even when the system is NOT yet low on memory,
but otherwise, logic's run-time performance should be same for both techniques.
See also below "Other tools" section for workaround.
Computers are really good at reading code; you shouldn’t worry about that. You may want to consider making your code more human-readable by using a class-property arrow-function.
If you want to keep both human-readability and performance, consider using plugin-transform-arrow-functions plugin, just run npm i --save-dev @babel/plugin-transform-arrow-functions
and add it into your "babel.config.js
" or ".babelrc
" file, like:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": false }],
["@babel/plugin-transform-arrow-functions", { "spec": true }]
]
}
Where
"spec": true
asks the plugin to auto transform arrow-functions, into method/function binding (which're inprototype
).Also, seems said plugin is not compatible with react-native (at least versions mentioned in linked post).
Or, you could use something like auto-bind decorator, which would turn above example into:
import React from 'react';
import { boundMethod as bind } from 'autobind-decorator';
class MyComponent extends React.Component {
constructor(props) {
super(props)
}
@bind
clickHandler() {
console.log( this )
}
render() {
return <button onClick={this.clickHandler}>Click Me</button>
}
}
Note that it's unnecessary to put
@bind
on every function. You only need to bind functions which you pass around. e.g.onClick={this.doSomething}
Orfetch.then(this.handleDone)
Upvotes: 72
Reputation: 1074258
Your second example recreates the wrapper function on every render
. In your first, you create the prototype function just once and create a bound function for it just once per instance, in the constructor.
As an alternative, you could just create the handler in the constructor as an arrow function:
class Click extends React.Component {
constructor(props) {
super(props);
this.clickEvent = () => { // ***
console.log(this); // ***
}; // ***
}
render = () => (
<button onClick={this.clickEvent}>Click Me</button>
);
}
It's very much a matter of style whether you do that or use bind
. (I use bind
so the function is on the prototype so we can mock it for testing purposes and such.)
Using the class fields proposal syntax (which is enabled in the transpiler settings of most React projects, and which you're using for your render
function), you can write that like this:
class Click extends React.Component {
constructor(props) {
super(props);
}
clickEvent = () => { // ***
console.log(this); // ***
}; // ***
render = () => (
<button onClick={this.clickEvent}>Click Me</button>
);
}
Which is the same thing. A separate clickEvent
function is created for every instance which closes over the instance. The two examples above do exactly the same thing (create the function and assign it to the instance just after the call to super()
in the constructor), the only difference is syntax.
Side note: You're creating a separate render
function for each instance of your class. There's no need to do that, it can be on the prototype. So:
class Click extends React.Component {
constructor(props) {
super(props);
}
clickEvent = () => {
console.log(this);
};
render() {
return <button onClick={this.clickEvent}>Click Me</button>;
}
}
Upvotes: 35