【问题标题】:When to use .toJS() with Immutable.js and Flux?何时将 .toJS() 与 Immutable.js 和 Flux 一起使用?
【发布时间】:2015-03-08 00:26:48
【问题描述】:

我正在尝试将 ImmutableJS 与我的 React / Flux 应用程序一起使用。

我的商店是Immutable.Map 对象。

我想知道我应该在什么时候使用 .toJS() ?应该是商店的.get(id) 返回的时候吗?或在带有.get('member') 的组件中?

【问题讨论】:

  • 好问题。不过,我不会在商店里这样做,因为从那时起,如果您想使用 shouldComponentUpdate 优化渲染,您将无法进行简单的对象比较 (prevState !== this.state)。
  • 谢谢,确实不要在商店中使用toJS()

标签: reactjs reactjs-flux immutable.js


【解决方案1】:

理想情况下,永远不要!

如果您的 Flux 商店正在使用 Immutable.js,那么请尝试一直维护。使用 React.addons.ReactComponentWithPureRenderMixin 来获得记忆性能的胜利(它添加了一个 shouldComponentUpdate 方法)。

渲染时,您可能需要调用 toJS(),因为 React v0.12.x 仅接受 Array 作为子代:

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

这在 React v0.13.x 中发生了变化。组件接受任何 Iterable 作为子项,而不仅仅是 Array。由于 Immutable.js 实现了 Iterable,你可以省略 toJS():

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

【讨论】:

  • Lee,你对 proptypes 有什么建议吗?我习惯使用arrayOf(shape...)
  • 现在如果只有我的外部库也支持 Immutable.js
  • 那么,我们是说在 React 组件中使用 Immutable.js 中的读取 API(get()getIn())是正确的做法吗?这感觉就像是泄漏的抽象,意味着如果我改变了我在存储中维护状态的方式,我必须触及每个读取值的组件。感觉有些不对劲……
  • @sethro Immutable.js 的重点是让你的 state 和 prop 值成为 Immutable.js 对象,然后实现 PureRenderMixin。如果你在 props/state 到达你的组件之前调用 toJS(),那么你将失去 PureRenderMixing 通过引用比较你的对象的能力,这是将不可变对象用于 React 组件和 PureRenderMixin 背后的核心思想。
  • @MatthewHerbst,我实际上并没有建议使用toJS(),这对我来说有同样的固有问题:将原本完全局限于商店的设计决策泄露到休息或您的应用程序。我知道无法使用传统的属性访问器是由于 Immutable.js 实现的限制;我只是指出这是一个很大的限制,除非您可以将库暴露给应用程序的组件,否则这些组件会知道企业知道该库。
【解决方案2】:

有点老问题,但最近我一直在尝试使用 reselectlodash's memoize 来尝试将可比较的对象返回给 React 的组件。

想象一下你有这样一家商店:

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)
);

然后我传递给一个TodoList组件todos作为prop,然后将其映射成两个TodoItem组件:

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;
    }

    // ...
}

这样,如果待办事项的存储中没有任何变化,当我调用 getTodos() 时,reselect 会返回给我相同的对象,因此不会重新渲染任何内容。

例如,如果 ID 为 2 的待办事项被标记为已完成,它也会在存储中更改,因此 todosSelector 返回一个新对象。然后通过memoizeTodo 函数映射待办事项,如果待办事项没有更改(因为它们是不可变映射),它应该返回相同的对象。因此,当TodoList 收到新的道具时,它会重新渲染,因为todos 已更改,但只有第二个TodoItem 重新渲染,因为表示 i​​d 为 1 的 todo 的对象没有更改。

这肯定会导致性能损失,特别是如果我们的商店包含很多商品,但我没有注意到我的中型应用程序有任何问题。这种方法的好处是您的组件接收纯 javascript 对象作为道具,并且可以将它们与PureRenderMixin 之类的东西一起使用,因此如何从商店返回对象不再是组件的业务。

【讨论】:

  • 惊人的解决方案!
【解决方案3】:

就像@LeeByron 说的,你不应该打电话给toJS。在 React 0.14.* 下,在不可变的 Map 上调用 map 将正常工作并呈现,但您最终会收到警告:

尚不完全支持将地图用作子项。这是一个实验性功能,可能会被删除。将其转换为键控 ReactElements 的序列/可迭代。

要解决这个问题,您可以在您的Map 上致电toArray(),例如:

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

将您的可迭代对象转换为数组并为 React 提供所需的内容。

【讨论】:

  • 感谢您找了这么久的回答!
【解决方案4】:

@Hummlas 提出的好观点。

我个人在我的 React 组件中使用它,当我迭代集合以呈现子组件数组时:

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

如果不使用 .toJS(),React 不会将映射的元素识别为组件数组。

【讨论】:

    【解决方案5】:

    -- 不再推荐--
    使用 Redux 时,我倾向于让我的连接的 mapStateToProps 函数使用 toJS() 转换不可变结构,并允许我的反应组件将道具作为 javascript 对象使用。

    【讨论】:

    • 但是 toJS 会返回一个新的 deepclone 对象。这对性能不利。
    • 是的,但是使用类似 reselect 的方法,只有当底层的不可变对象发生变化时,才能返回一个新对象。唯一的问题是嵌套数据。例如,如果地图列表中只有一个元素发生了变化,则将返回一个包含新对象的全新数组,从而在每个子视图中触发渲染。在不可变的情况下,只有发生变化的孩子才会被重新渲染......
    • 由于性能问题,我已停止执行我的建议。
    • @WalkerRandolphSmith 那么你现在在做什么?我遇到了同样的问题并得出了相同的解决方案。
    • 我使用不可变记录,所以接口(点符号)不会从 js 对象改变。
    猜你喜欢
    • 2017-01-27
    • 1970-01-01
    • 1970-01-01
    • 2017-09-18
    • 2018-08-14
    • 2015-12-08
    • 2015-05-26
    • 1970-01-01
    • 2017-09-16
    相关资源
    最近更新 更多