【问题标题】:Proper handling of multiple sequential state updates正确处理多个顺序状态更新
【发布时间】:2014-11-28 19:07:40
【问题描述】:

这个例子有点做作,但请耐心等待。假设我有两个函数作为 React 组件中的成员:

var OrderApp = React.createClass({
    getInitialState: function() {
        return {
            selectedOrders: {},
            selectedLineItems: {}
        };
    },

    setOrderSelected: function(order, newSelectedState) {
        var mutation = {};
        mutation[order.id] = {$set: newSelectedState};
        var newSelectedOrders = React.addons(this.state.selectedOrders, mutation);
        _.each(order.lineItems, function(lineItem) {
            setLineItemSelected(lineItem, newSelectedState);
        });
        this.setState({selectedOrders: newSelectedOrders});
    },

    setLineItemSelected: function(lineItem, newSelectedState) {
        var mutation = {};
        mutation[lineItem.id] = {$set: newSelectedState};
        var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);
        this.setState({seelctedLineItems: newSelectedLineItems});
    }
});

在调用 OrderApp.setOrderSelected(order, true) 完成时,我的组件状态中似乎只有最后一个 lineItem 选定状态发生了变化。我猜这是因为 React 正在批量调用 setState,所以当我调用时:

var newSelectedLineItems = React.addons(this.state.selectedLineItems, mutation);

在第二个函数中,我每次都取原始状态,所以只有最后一个突变生效。

我应该收集父函数中的所有状态突变并且只调用一次 setState 吗?这对我来说有点笨拙,我想知道我是否缺少更简单的解决方案。

【问题讨论】:

    标签: javascript immutability reactjs


    【解决方案1】:

    您决不能在组件内的给定执行线程中多次调用this.setState

    所以你有几个选择:

    使用 forceUpdate 代替 setState

    您不必使用this.setState - 您可以直接修改状态并在完成后使用this.forceUpdate

    这里要记住的主要事情是,一旦开始直接修改this.state,就应该将其视为已污染。 IE:在您调用this.forceUpdate 之前,不要使用您知道已修改的状态或属性。这通常不是问题。

    当您处理this.state 中的嵌套对象时,我绝对更喜欢这种方法。就个人而言,我不是React.addons.update 的忠实粉丝,因为我发现语法非常笨拙。我更喜欢直接修改forceUpdate

    创建一个您传递的对象,直到最终在 setState 中使用它

    收集所有更改的对象,在执行线程结束时传递给this.setState

    【讨论】:

    • 这与所有应用程序逻辑混乱,使用 forceUpdate 时没有任何显示
    【解决方案2】:

    不幸的是,这是浅合并的问题。您必须收集更新,并一次性应用它们。

    对我来说有点笨拙

    这是一个非常好的迹象,表明您应该使用 mixin!这段代码看起来很糟糕,但你可以在使用时假装它看起来很漂亮。

    var React = require('react/addons');
    var UpdatesMixin = {
        componentDidMount: function(){
            this.__updates_list = [];
        },
        addUpdate: function(update){
            this.__updates_list.push(update);
            if (this.__updates_list.length === 1) {
             process.nextTick(function(){
              if (!this.isMounted()) {
                this.__updates_list = null;
                return;
              }
    
              var state = this.__updates_list.reduce(function(state, update){
                return React.addons.update(state, update);
              }, this.state);
    
              this.setState(state);
              this.__updates_list = [];     
            }.bind(this));
          }
        }
    };
    

    requirebin

    它缓冲所有同步发生的更新,将它们应用到 this.state,然后执行 setState。

    用法如下所示(完整演示请参见 requirebin)。

    this.addUpdate({a: {b: {$set: 'foo'}};
    this.addUpdate({a: {c: {$set: 'bar'}};
    

    有些地方可以优化(例如,通过合并更新对象),但您可以稍后再进行优化,而无需更改组件。

    【讨论】:

    • 作为 React 的新手,我没有考虑在这里使用 Mixin,但这绝对感觉更干净。我的一个问题涉及到您的解决方案中对 process.nextTick 的调用。我能找到的唯一文档表明这是一个特定于节点的库。在客户端处理时,是否有类似的库可以用来缓冲更新?
    • 如果你不使用 browserify/webpack,你可以下载一个独立的 shim:wzrd.in/standalone/process%2fbrowser - 大多数人使用 cjs 模块,你必须自己编译一些库,因为 npm 经常唯一的分发媒介。
    • 我真的很喜欢你对我问题的回答。我将 Mike 的答案标记为已接受,只是因为它不需要外部依赖项。我认为有充分的理由证明您的答案是从更多的 ReactJS 思维方式来解决问题的。感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    • 2020-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多