How Immutable.js works with React and Redux

In this chapter, we will start to learn Immutable, contains follow aspects: what’s the Immutable, why we need it, what it provide to us, how Immutable works well with React, redux, and the issues when using it.

Immutable

It comes from the functional programming world.

Think of follow codes:

var object = { x:1, y: 2 };
var object2 = { x: 1, y: 2 };
object == object2// false
object === object2 // false

The Equality check will contains two part:

  1. Value check;
  2. Reference check;

Reference equality

Js objects are rather complicated data structures, it’s keys can point to arbitrary values, include object.The object were stored in the memory(have a physical address) and it return a reference, then we assign the reference to a variable.

Value equality

Just check whether the value is same or not.

React re-render

React update and rerender the component accroding to the props and states, if the state object become too large, the performance will be downed.When object properties changed, the equality check will be hard.

  1. For a nested object, we must iterable the object to check, it will cost too much time;
  2. When we change an object, it’s reference stays the same, it don’t change the result of the reference equality check.

Immutable provide a easier way to know whether the object changed or not.

What is the Data Immutable?

Never mutated, instead copy it and then make change.The differences between it with the JavaScript plain object can extract to follow two aspects:

  1. Persistent data structures
  2. Structures sharing(Trie)

Persistent Data structures

Persistent data structures confirm that all operations will return a newer copy of that data structure and keep the original structure intact, instead of changing the original structure. And the Immutable using the Trie for constructing it’s persistent data structures, like a tree.

Structures sharing

Once a trie is created, we can think it as a tree, such as the follow image, it will reuse the node as much as possible.

Structures sharing

When we update a property in a Immutable object, the property mapes to a node in the trie tree, then it just need reconstruct the specific node and affected by the specific node(it’s ascendent nodes), and the other nodes can be reused.

External Link

  1. Immutable Persistent Data Structures

Why / When to use Immutable

Immutability, side effects, and mutation

Mutation is discouraged because it generally breaks time-travel debugging, and React-Redux’s connect function:

  1. For time traveling, the Redux DevTools expect that replaying recorded actions would output a state value, but not change anything else. Side effects like mutation or asynchronous behavior will cause time travel to alter behavior between steps, breaking the application.
  2. For React-Redux, connect checks to see if the props returned from a mapStateToProps function have changed in order to determine if a component needs to update. To improve performance, connect takes some shortcuts that rely on the state being immutable, and uses shallow reference equality checks to detect changes. This means that changes made to objects and arrays by direct mutation will not be detected, and components will not re-render.

Other side effects like generating unique IDs or timestamps in a reducer also make the code unpredictable and harder to debug and test.

The components will make error re-render or may don’t re-render when they should and we can hardly tracking the bug, all those are because a Redux reducer will return a mutated state object, we can’t tracking and predict the mutated state object. Then the Immutable.js can provide us the Immutable way to solve these problems and it’s rich API is convenient to use.

external Link

  1. Why and When to use Immutable
  2. Why do we need Immutable class

How to use Immutable

Immutable can provide significant performance improvements to our application, but we must use it correctly. There are too many immutable libraries we can use, for react the Immutable.js is our first option.

Immutable.js with React

Note that React state must be a plain JS object, and not an Immutable collection, because React’s setState API expects an object literal and will merge it (Object.assign) with the previous state.

class  Component  extends React.Component {
    Constructor (props)  {
        super(props)

        this.state = {
            data: Immutable.Map({
            count:0,
            todos: List()
            })
        }
        this.handleAddItemClick =       this.handleAddItemClick.bind(this)
    }

    handleAddItemClick () {
        this.setState(({data}) => {
            data: data.update('todos', todos => todos.push(data.get('count')))
        })
    }

    render () {
        const data = this.state.data;
        Return (
            <div>
                <button onclick={this.handleAddItemClick}></button>
                <ul>
                    {data.get('todos').map(item =>
                         <li>Saved:
                        {item}</li>
                     )}
                </ul>
            </div>
        )
    }
}
  1. Using the immutable.js read API to read state or prop, eg: get(), getIn();

  2. Using Immutable.js collections for children elements:

    Using higher-order functions such as map(), filter() to produce the React element’s children elements.

    Note: If you’re using React v0.12, you must add .toArray() after the call to .map() to convert the Immutable collection to a JavaScript Array. The ability to use any Iterable as React children was added in React v0.13.

  3. Using Immutable.js update API to uodate state or prop, eg: update(), set():

    this.setState(({data}) => ({
        data: data.update('count', v => v + 1)
    }));
    

    Equal to follow:

    this.setState(({data}) => ({
        data: data.set('count', data.get('count') + 1)
    }));
    

External Link:

  1. Immutable as React state

Immutable.js with Redux

Next, we are going to introduce how Immutable.js works and works well with Redux, we can follow these opinionated best practices.

redux-immutable

Original Redux combineReducers expects a plain object and it treats state as a plain object.When createStore is called with Immutable initialState, the reducer will return an error:

if   (!isPlainObject(inputState)) {
    return   (                              
        `The   ${argumentName} has unexpected type of "` +                                    ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      ".Expected argument to be an object with the following + 
      `keys:"${reducerKeys.join('", "')}"`   
    )  
}

redux-immutable is used to create an equivalent function of Redux combineReducers that works with Immutable.js state.

const StateRecord = Immutable.Record({
    foo: 'bar'
 });
const rootReducer = combineReducers({
  first: firstReducer
}, StateRecord);
react-router-redux

routeReducer also does not work with Immutable, we must custome a new reducer:

import Immutable from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';

const initialState = Immutable.fromJS({
   locationBeforeTransitions: null
});

export default (state = initialState, action) => {
   if (action.type === LOCATION_CHANGE) {
     return state.set('locationBeforeTransitions', action.payload);
   }

    return state;
 };

When connect the hsitory to the store, we should convert the routing payload to a JavaScript object through pass the selectLocationState parameter to the syncHistoryWithStore function:

import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';

const history = syncHistoryWithStore(browserHistory, store, {
   selectLocationState (state) {
       return state.get('routing').toJS();
    }
});

Best practice

When use Immutable with Redux, we can refer to the follow optional best practice.

  1. Never mix plain JavaScript Objects with Immutable collections.

    Never let plain JavaScript Objects and Immutable collections mix in.

  2. Make the entire Redux state tree an Immutable object

    For a Redux application, the entire state tree should be a Immutable object, without plain object.

    1. Using it’s fromJS() to create the state tree;

      The state can be an instance of Immutable.Record or Immutable.Collection that implements get, set and withMutations methods, eg: List, Map, OrderedMap.

    2. Adapting the combineReducers function to work with Immutable through redux-immutable, refer to the former redux-immutable.

  3. When adding JavaScript object to Immutable object, convert the object to an Immutable object using fromJS(), and then using Immutable update(), merge(), set() methods update.

    // avoid
    const newObj = { key: value }
    const newState = state.setIn(['prop1'], newObj)
    // newObj has been added as a plain JavaScript object, NOT as an Immutable.JS Map
    
    // recommended
    const newObj = { key: value }
    const newState = state.setIn(['prop1'], fromJS(newObj))
    
  4. Using Immutable everywher except the presentation component.

    To keep application’s performance, always using Immutable in container components, selectors, sagas and thunks, reducers and action, but don’t use it in presentation components.

  5. Using Immutable in container components(smart components).

    The container components access the store via react-redux’s connect function, so make sure the selectors always return Immutable objects, otherwise it will lead to unnecessary rerender.And we may can memorize the selectors using a third library like reselect.

  6. Limiting the use of toJS().

    It’s an expensive function, and it is anti-Immutable.

    1. Never use toJS() in mapStateToProps

      The toJS() will return a new Plain object every time it’s invoked,.If use it in mapStateToProps, the component will think that the object always changed every time the state tree changed, although the object actually make no changes, and then lead to the unnecessary rerender.

    2. Never use toJS() in presentation components(dumb components).

      If pass an Immutable prop to the component, the component depend on the Immutable to render.That will make tesing, reusing and refactoring difficult.

  7. Using a Higher-Order Component to convert the container component’s Immutable props to the presentation component’s props of plain object.

    We say that limit the use of toJS(), when we want to map the immutable props in container component to plain JavaScript props in the presentation component what should we do? We shouldn’t use the toJS() in the mapStateToState just like what we have said up, we can create a Higher-Order Component(HOC) to convert the props using toJS(), and then it will be passed to the presentation component.

    Refer to follow code:

    import React from 'react'
    import { Iterable } from 'immutable'
    
    export const toJS = WrappedComponent => wrappedComponentProps => {
    const KEY = 0
       const VALUE = 1
    const propsJS = Object.entries(wrappedComponentProps)
       .reduce((newProps, wrappedComponentProp) => {
        newProps[wrappedComponentProp[KEY]] =   Iterable.isIterable(wrappedComponentProp[VALUE]) ? wrappedComponentProp[VALUE].toJS() : wrappedComponentProp[VALUE]
            return newProps
    }, {})
    
    return <WrappedComponent {...propsJS} />
    }
    

    Usage in container component:

    import { connect } from 'react-redux'
    import { toJS } from './to-js'
    import DumbComponent from './dumb.component'
    
    const mapStateToProps = state => {
    return {
         // obj is an Immutable object in Smart Component, but it’s converted to a plain
         // JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
         // object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
         // there is no issue with errant re-renderings.
        obj:getImmutableObjectFromStateTree(state)
      }
    }
    
    export default connect(mapStateToProps)(toJS(DumbComponent))
    

    That Higher-Order Component will not cause much performance degradation as the component will only be called when the connected component’s props change.

    Note: Maybe you ever think that wether using toJS() in HOC will negate the performance and you suggest to keep the Immutable object in the presentation component.However, for most time, the benefits of keeping Immutable out of the presentation components, such as maintainability, portability and easier testing, will exceed the performance improvements of using it in.

External Link:

  1. Immutable.js Best practices

The issues with Immutable

There is nothing absolutely perfect.When using Immutable we may meet some issues, we can conclude some solution.

  1. Difficult to interoperate with.

    As Immutable is different to the plain JavaScript object, it’s difficult for us to interoperate with the two sometimes.

    1. We must modify the way to access the property via get() or getIn() instead directly use dot or bracket notation.
    2. No destructuring and spread operators.
    3. It’s difficult to cooperate with some third library such as lodash or jQuery.
  2. Permeate through the entire codebase.

    The Immutable code will permeate through the entire codebase. The tight external dependencies will make it difficult to remove and replace at some point.

  3. Not suitable for small state that change often

    Immutable works best for large collections of data, but for some data than is small and changed often, it won’t perform well.

  4. Breaks object references will cause poor performance

    The major advantages of Immutable is the shallow equality check which obviously improve performance.

    When we called the toJS() twice, the equality check on those two results will fail even though the object haven’t changed.For example,

    We use the toJS() in mapStateToProps, it will be poor performance, so we should create a new Higher Order Component that we have introduced before.

  5. Difficult to Debug

    Immutable object is difficult to debug as inspecting such an object will reveal an entire nested hierarchy of Immutable.JS-specific properties.You can use a browser extension such as the Immutable.js Object Formatter to resolve this issue.

References

  1. Immutable-js
  2. Redux and Immutable.js
  3. redux-immutable
  4. Pros and cons of using Immutability with react

原创文章,转载请注明: 转载自 熊建刚的博客

本文链接地址: How Immutable.js works with React and Redux

熊 建刚

热爱前端,但不局限于前端,喜欢尝试各种新技术,爱好读书。

发表评论

电子邮件地址不会被公开。 必填项已用*标注