React:用还是不用 Immutable.js

React: use or not to use Immutable.js

我在使用 React.js 时寻找 Immutable.js 动力。据我了解 React.js 应该保证属性的不变性。但是,我可以更改视图属性 in this demo:

var Hello = React.createClass({
    render: function() {
        var item = { prop: 128 };
        return <Test item={item} />;
    }
});

var Test = React.createClass({

    render: function() {
        this.props.item = -2; // Don't throw any error
        return <div>{this.props.item}</div>; // Returns -2 instead of object
    }
});

React.render(<Hello name="World" />, document.getElementById('container'));

更新:感谢@FelixKling,现在我知道自 React v0.14(即将推出)以来属性将是不可变的。

问题:使用Immutable.jsReact v0.14的动机是什么?


Update2: 我们真的可以改变 parent 的属性吗?在子组件中

let somePropVal = { a: 1 };
<SubComponent someProp={somePropVal} />
//....Then in SubComponent
this.props.someProp.a = 2; // somePropVal.a still will be `1` at parent element

immutable.js 使用 PureRenderMixin 提升反应性能

PureRenderMixin 来源:

var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return !shallowEqual(this.props, nextProps) ||
           !shallowEqual(this.state, nextState);
  }
};

与immutable.js比较shallowEqual中的两个对象非常快

在使用 React 纯渲染面对所有耙子之后,我想说 Immutable.js 与提供 deepEqual 方法无关。重点是在每次修改时克隆状态。

为什么每次修改都需要克隆状态?

一切顺利,直到视图的状态仅包含原始值。但是有一刻可能会发生它是一个数组或复杂的对象树。

假设您有一个视图 YourView,它从商店 YourStore 获取一个数组。并且您不想使用 Immutable.js,只需包含 Node.LS 的 deepEqual。例如:

PureRenderMixin.js

const deepEqual = require('deep-equal');

module.exports = function pureRenderMixin(Component) {
    Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
        return !deepEqual(this.props, nextProps) || !deepEqual(this.state, nextState);
    };
    return Component;
};

YourView.react.js

class YourView extends React.Component {

    constructor(props) { 
        super(props);
        this._onChange = this._onChange.bind(this);
    }

    componentWillMount() {
        YourStore.addChangeListener(this._onChange);
    }

    _onChange() {            
        this.setState({array: YourStore.getArray()});
    }
}

module.exports = PureRenderMixin(YourView);

YourStore.js

......
getArray() { return _array; }

switch(action.type) {
   case ActionTypes.UPDATE_USER_FLAG:
       _array[action.index].value= action.flag; // BUG!!!
       YourStore.emitChange();
       break;
}

问题 #1:shouldComponentUpdate return false 而不是 true 你期望 _array[action.index].value = action.flag; 会更新 YourView,但它不会.不是因为 shouldComponentUpdate 会 return false

原因是数组只是一个引用,在this.setState({array: YourStore.getArray()})的时刻this.state.array(之前的状态)也更新了。这意味着在 shouldComponentUpdate 方法内部 this.state & nextState refs 将指向同一个对象。

问题1#解决方法:

您需要在更新之前复制数组引用(即在 lodash 的帮助下):

YourStore.js

......
getArray() { return _array; }

switch(action.type) {
   case ActionTypes.UPDATE_USER_FLAG:
       _array = _.cloneDeep(_array); // FIXED, now the previous state will differ from the new one
       _array[action.index].value= action.flag; // BUG!!!
       YourStore.emitChange();
       break;
}

问题 #2:有时您需要多次克隆 _array,代码会出错

假设您需要在 if 语句中更新 _array 的多个值:

   case ActionTypes.UPDATE_USER_FLAG:
       // You can't clone the _array once, because you don't know which conditions will be executed
       // Also conditions may not be executed at all, so you can't clone the _array outside of if statements
       if (someCondition) {
           _array = _.cloneDeep(_array);
           _array[someIndex].value= action.flag;
       }
       if (anotherCondition) {
           _array = _.cloneDeep(_array);
           _array[anotherIndex].value= action.flag;
       }
       YourStore.emitChange();
       break;

问题 #2 解决方案: 使用 Immutable.js。好处:

  1. 它有清晰的界面,让你的大学清楚地知道每次应该克隆状态
  2. 它有批量更新,所以你不必担心多次克隆数组