Since React has moved on to JavaScript 2015 a common example of handling a button click to change state often looks something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import React, { Component } from 'react'; "use strict" export default class CounterComponent extends Component { constructor(...args) { super(...args); this.state = { value: 0 } } render() { return ( <span> Value: { this.state.value } <input type="button" value="Increment" onClick={ () => { this.setState({ value: this.state.value + 1}) }} /> </span> ); } } |
The arrow function on line 25 has a big advantage in JavaScript 2015 over the closure definitions from version 5: when it is created "this" is bound to the same object where it was created, the instance of the CounterComponent. But it is still a closure, and the problem is that a new function is created every time the render method is called.
Closures are a function definition found inside of a function call, and the definition is re-evaluated every time the function is called. In the React world, render is called every time a component needs to be re-evaluated. A lot of times. If render is called a thousand times, a thousand versions of the arrow function are going to be created. Now, multiply this over all of the components in your React application, and each of the closures that their render methods create!
Of course, each one of these arrow functions will be eligible for garbage collection almost as soon as the next one is created. But we still have to ask are:
- How efficient is it to keep making these functions?
- Can garbage collection keep up with everything that we create?
The solution is to replace the arrow function with a reference to a function that is not re-created for every call to render. Since we do not want functions floating around in global space, preferably a method of the CounterComponent class as on line 32:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import React, { Component } from 'react'; "use strict" export default class CounterComponent extends Component { constructor(...args) { super(...args); this.state = { value: 0 } this.increment = this.increment.bind(this); } render() { return ( <span> Value: { this.state.value } <input type="button" value="Increment" onClick={ this.increment } /> </span> ); } increment() { this.setState({ value: this.state.value + 1}); } } |
On line 27 of this example the arrow function has been replaced with the reference to the method. But by itself that creates another problem, when the method is called "this" is not bound to the CounterComponent instance when the function is called! Clicking on the increment button will not have any effect in the program, and if you check the console you will find errors where JavaScript cannot find this.setState because this is not a valid instance of CounterComponent.
That problem may be fixed with the bind method of the the function object: this.increment.bind(this). That creates a new version of the function where this is bound to the CounterComponent instance, which happens to be referenced by this when the bind method is called. When the new function is called, this will always be the CounterComponent instance no matter who invokes it.
But do not use bind on line 27, or you will fall right back to creating a new "bound" version every time render is called. Do it once for the instance in the constructor, as on line sixteen. Think about this: before line sixteen the reference to the method increment is actually a reference to the method in the prototype object for CounterComponent and shared by all instances of the class (if you are weak on prototypes, take a look at this post on JavaScript Inheritance Best Practices).
When you re-assign the reference in the constructor you are creating a new property in the instance that overrides the prototype method, and the new property is a reference to the bound function-object. That is an additional function-object, but at least only one is created for the instance and used every time the render method is invoked.
So, this solution is a little more complicated than using the arrow functions. But with just a little more work we can save the JavaScript engine from creating and garbage-collecting potentially thousands or even tens of thousands of objects.
The full code for the working examples is available at: http://askjoelit.com/download/ReactCallbacks.zip. The two projects the zip file contains are wrapped with a Node-express application. Remember to use "npm install" and "bower install" to retrieve the dependencies before launching the servers with "npm start."
No comments:
Post a Comment