Chapter 16. Immutability

An increasingly popular way to use React is in conjunction with immutable data structures—that is, data structures that never change after they are instantiated.

Whether immutability is right for your application, and if so, which immutability library to adopt, depends on the specifics of your use case. In this chapter we will cover the benefits and costs of immutability, and look at three different libraries you can use to incorporate it into your React application.

Performance Benefits

The most clear-cut benefit of immutability in a React application is what it does for shouldComponentUpdate. Although shouldComponentUpdate can provide great performance improvements, those improvements are often eroded or even erased as the shouldComponentUpdate handler itself increases in complexity. 

Suppose you have a shouldComponentUpdate, which only needs to check one field in props to determine whether it will return true or false. That’s going to run quickly! Now suppose instead it needs to check a dozen fields instead, and some of those fields contain object which each require multiple comparisons, since all that checking can add up fast.

The root of the problem here is that it is time-consuming to determine whether two mutable objects represent equivalent values. But what if instead that were a quick check?

With immutable props and state, there is indeed a quick check you can use. When your props are represented as an immutable object, then they can no longer be updated in place; to get new props, you must instantiate a new immutable object and replace the old object with the new. As such, using oldProps !== newProps as your shouldComponentUpdate check will very quickly tell you if you definitely need to update.

It’s worth noting that although this check is very fast, it can generate false positives. For example, if your old state were { username: "Don Jacko", active: true } and you invoked replaceState passing a freshly-instantiated object { username: "Don Jacko", active: true }, the old state would clearly be identical to the new state, , but a shouldComponentUpdate function using oldState !== newState as its test would still consider them different, and would re-render unnecessarily.

In practice, this rarely seems to come up, and if it does, the only cost would be an unnecessary re-render. False negatives (in which a component actually needed to re-render but determined that it did not) would be a much greater concern, but fortunately that problem does not manifest with this approach.

Performance Costs

Although immutable data structures can get you dead-simple and lightning-fast shouldComponentUpdate implementations, which save considerable rendering performance, they are not without their performance costs. Whereas, mutable objects are quick to update but slower to compare, immutable objects are quick to compare but slower to update.

Instead of making the change in place, the immutability library you’re using must instantiate at least one new object, and modify it as necessary until it matches the old object in every way except for the one changed field. This costs time both during the update process and during garbage collection, when the additional objects are inevitably cleaned up.

For the vast majority of React applications, this is an excellent tradeoff to make. Remember that a single update typically results in a call to many render functions, and that render functions instantiate quite a few objects themselves in the course of building up their return values. Sacrificing a bit of update speed to save numerous render function calls is almost always going to be a performance win.

Separate from performance, another cost to consider is that you lose the convenience of setState and setProp. Since both of these rely on being able to mutate your state and props objects, respectively, when you switch those to immutable objects, your only options become replaceState and replaceProps.

Architectural Benefits

Besides performance benefits, there are additional architectural benefits that come from using immutable data more. These benefits extend beyond props and state, and apply more broadly to your application as a whole.

Immutable data is generally less error-prone to work with than mutable data. When passing mutable data from one function to another, and expecting that it will come back unchanged, you have two options: one is to clone the object ahead of time and pass the clone, and the other is to cross your fingers and hope the other function doesn’t modify it. (Because if it does, you’re in for some bug hunting!)

Defensive cloning is effective in situations like these, but generally less efficient than what an immutable library would do in the same spot. Knowing that your initial data is already assumed to be immutable can allow for faster cloning, than when it’s mutable the whole way through.

Embracing immutability means you do not have to remember to defensively clone on a case-by-case basis; instead, you will get the same safe-to-pass characteristics by virtue of using immutable data in the usual way. This makes immutable data less error-prone to work with.

Using the Immutability Helpers Addon

The easiest way to introduce immutability to your React application is to use the Immutability Helpers Addon. It lets you continue using your existing mutable data structures as though they were immutable, by making it easy to create new (also mutable) objects instead of performing in-place updates.

While this does not actually give you any new guarantees, it does allow you to write a quick shouldComponentUpdate function as described above.

Let’s update our example to use the Immutability Helpers Addon:

var update = React.addons.update;

var SurveyEditor = React.createClass({
  // ...

  handleDrop: function (ev) {
    var questionType = ev.dataTransfer.getData('questionType');
    var questions = update(this.state.questions, {
      $push: [{ type: questionType }]
    });

    this.setState({
      questions: questions,
      dropZoneEntered: false
    });
  },

  handleQuestionChange: function (key, newQuestion) {
    var questions = update(this.state.questions, {
      $splice: [[key, 1, newQuestion]]
    });

    this.setState({ questions: questions });
  },

  handleQuestionRemove: function (key) {
    var questions = update(this.state.questions, {
      $splice: [[key, 1]]
    });

    this.setState({ questions: questions });
  }

  // ...
});

Next we’ll try a different library, one which actually does make guarantees about immutability.

Using seamless-immutable

The seamless-immutable library is not part of the official React family of libraries, but was designed to be used with React. It creates immutable versions of regular Objects and Arrays, which can be passed around, accessed, and iterated over just like their mutable counterparts, but which block any operations that would mutate them.

Like the Immutability Helpers Addon, seamless-immutable provides convenience functions for working with immutable data. In the case of seamless-immutable, however, these are implemented as methods on the objects themselves. For example, seamless-immutable objects gain a .merge() method which works like the $merge option in the Immutable Helpers Addon.

var update = React.addons.update;

var SurveyEditor = React.createClass({
  // ...

  handleDrop: function (ev) {
    var questionType = ev.dataTransfer.getData('questionType');
    var questions = this.state.questions.concat(
      [{ type: questionType }]
    );

    this.replaceState(this.state.merge({
      questions: questions,
      dropZoneEntered: false
    }));
  },

  handleQuestionChange: function (key, newQuestion) {
    var questions = this.state.questions.map(
        function(question, index) {
            return index === key ? newQuestion : question;
        }
    );

    this.setState({ questions: questions });
  },

  handleQuestionRemove: function (key) {
    var questions = this.state.questions.filter(
        function(question, index) {
            return index !== key;
        }
    );

    this.setState({ questions: questions });
  }

  // ...

});

Using Immutable.js

Whereas the previous two libraries provided tools around normal JavaScript objects and arrays, Immutable.js takes a different approach: it provides alternative data structures to objects and arrays.

The most commonly-used of these data structures are Immutable.Map (analogous to Object) and Immutable.Vector (analogous to Array). Neither of these can be freely substituted for JavaScript objects or arrays in the general case, although you can easily convert to and from the Immutable.js data structures and their mutable JavaScript counterparts.

Immutable.Map

Immutable.Map can be used as a substitute for regular JavaScript objects:

var question = Immutable.Map({description: 'who is your favorite superhero?'});
// get values from the Map with .get
question.get('description');

// updating values with .set returns a new object.
// The original object remains intact.
question2 = question.set('description', 'Who is your favorite comicbook hero?');

// merge 2 objects with .merge to get a third object.
// Once again none of the original objects are mutated.
var title = { title: 'Question #1' };
var question3 = question.merge(question2, title);
question3.toObject(); // { title: 'Question #1', description: 'who is your favorite comicbook hero' }

Immutable.Vector

Use Immutable.Vector for arrays:

var options = Immutable.Vector('Superman' , 'Batman');
var options2 = options.push('Spiderman');
options2.toArray(); // ['Superman', 'Batman', 'Spiderman']

You can also nest the data structures:

var options = Immutable.Vector('Superman' , 'Batman');
var question = Immutable.Map({
  description: 'who is your favorite superhero?',
  options: options
});

Immutable.js has many more facets. For more information on Immutable.js go to https://github.com/facebook/immutable-js.

Summary

In this chapter we learned about the benefits and costs of using immutability in a React application. We covered its impact on shouldComponentUpdate, on what it means for update times, and three different ways to introduce immutability into a React code base.

Next we’ll look into other uses of React beyond traditional web applications.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.148.186