When to use .toJS() with Immutable.js and Flux?

感情迁移 提交于 2019-12-02 17:16:07
Lee Byron

Ideally, never!

If your Flux stores are using Immutable.js then try to maintain all the way through. Use React.addons.ReactComponentWithPureRenderMixin to achieve a memoization performance win (it adds a shouldComponentUpdate methods).

When rendering, you may need to call toJS() as React v0.12.x only accepts Array as children:

render: function () {
  return (
    <div>
      {this.props.myImmutable.map(function (item) {
        <div>{item.title}</div>
      }).toJS()}
    </div>
  );
}

This have changed in React v0.13.x. Components accept any Iterable as children instead of only Array. Since Immutable.js implements Iterable, you are able to omit the toJS():

render: function () {
  return (
    <div>
      {this.props.myImmutable.map(function (item) {
        <div>{item.title}</div>
      })}
    </div>
  );
}
Ingro

Kinda old question but lately I've been experimenting with this approach using reselect and lodash's memoize in the effort of returning comparable objects to React's Components.

Imagine you have a store like this:

import { List, Map } from 'immutable';
import { createSelector } from 'reselect';
import _ from 'lodash'; 

const store = {
    todos: List.of(
        Map({ id: 1, text: 'wake up', completed: false }), 
        Map({ id: 2, text: 'breakfast', completed: false })
    )
};

const todosSelector = state => state.todos;

function normalizeTodo(todo) {
    // ... do someting with todo
    return todo.toJS();
}

const memoizeTodo = _.memoize(normalizeTodo);

export const getTodos = createSelector(
    todosSelector,
    todos => todos.map(memoizeTodo)
);

Then I pass to a TodoList component todos as a prop, which will then be mapped into two TodoItem Components:

class TodoList extends React.Component {
    shouldComponentUpdate(nextProps) {
         return this.props.todos !== nextProps.todos;
    }

    render() {
       return (<div>
           {this.props.todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
       </div>);
    }
}

class TodoItem extends React.Component {
    shouldComponentUpdate(nextProps) {
        return this.props.todo !== nextProps.todo;
    }

    // ...
}

This way, if nothing changed in the todo's store, when I call getTodos() reselect returns to me the same object, and so nothing gets re-rendered.

If, for example, todo with id 2 is marked as completed, it also changes in the store, and so a new object is returned by todosSelector. Then the todos are mapped by memoizeTodo function, which should return the same object if a todo isn't changed (since they are Immutable Maps). So when TodoList receives the new props, it re-renders because todos has changed, but only the second TodoItem re-renders because the object representing the todo with id 1 didn't change.

This surely could lead to a performance loss, especially if our store contains a lot of items, but I didn't notice any problem in my medium-size app. The upside of this approach is that your Components receives plain javascript objects as props and could use them with something like PureRenderMixin, so how objects are returned from the store is not business of Components anymore.

Like @LeeByron said, you shouldn't have to call a toJS. Under React 0.14.*, calling map on an Immutable Map will work and render correctly, but you will end-up with a warning:

Using Maps as children is not yet fully supported. It is an experimental feature that might be removed. Convert it to a sequence / iterable of keyed ReactElements instead.

To deal with this, you can call toArray() on your Map like:

render () {
  return (
    <div>
      {this.props.immutableMap.toArray().map(item => {
        <div>{item.title}</div>
      })}
    </div>
  )
}

Converting your iterable to an array and giving React what it wants.

Good point raised by @Hummlas.

I personnally use it in my React components, when I iterate over a collections to render an array of sub components:

this.props.myImmutableCollection.map(function(item, index) {
    React.DOM.div null, item.get('title');
}).toJS();

If you don't use .toJS(), React won't recognize the mapped elements as an array of components.

-- No longer recommend --
When using Redux I tend to let my connects' mapStateToProps function transform immutable structures using toJS() and allow my react components to consume the props as javascript objects.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!