React - 在不使用setState的情况下更改状态:必须避免它吗?

时间:2017-02-06 05:07:05

标签: javascript arrays reactjs state setstate

我的代码有效,但我有一个最佳实践问题:我在状态中有一个对象数组,用户交互将一次更改一个对象的值。据我所知,我不应该直接改变状态,我应该总是使用setState。如果我想以任何代价避免这种情况,我将通过迭代深度克隆数组,并更改克隆。然后将状态设置为克隆。在我看来,避免改变我以后会改变的状态只会降低我的表现。

详细版本: this.state.data是一个对象数组。它代表论坛中的主题列表,收藏夹按钮将切换,调用clickCollect()。 由于我在状态中有一个数组,当我更改一个项的is_collected属性时,我需要创建一个要使用的数组的副本,并在更改为新值后,我可以将其设置为状态。

var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});

var data = this.state.data:这会将指针复制到数组,而push(),shift()等会直接改变状态。 datathis.state.data都会受到影响。

var data = this.state.data.slice(0):这使得浅层克隆,推送和移位不会改变状态,但在我的克隆中,我仍然有指向状态数组元素的指针。因此,如果我更改data[0].is_collectedthis.state.data[0].is_collected也会更改。这是在我致电setState()之前发生的。

通常我应该这样做:

var data = []; 
for (var i in this.state.data) {
    data.push(this.state.data[i]); 
}

然后我更改index的值,当它为false时将其设置为true,否则为true:

data[index].is_collected = !data[index].is_collected;

改变状态:

this.setState({data: data});

考虑我的阵列相对较大或非常大,我想这次迭代会降低我APP的性能。如果我知道这是出于任何原因的正确方法,我会支付这笔费用。但是,在这个函数(clickCollect)中,我总是将新值设置为状态,我不是在等待一个错误的API响应,它会说停止进行更改。在所有情况下,新值都将进入状态。实际上,我只是为了再次渲染UI而调用setState。所以问题是:

  1. 在这种情况下我是否必须创建深度克隆? (for var i in ...
  2. 如果没有,如果我的数组包含对象,那么制作浅层克隆(.slice(0))是否有意义?正在对数组内部的对象进行更改,因此浅层克隆仍会更改我的状态,就像副本(data = this.state.data)一样。
  3. 我的代码已经简化,为了简单起见,API调用被删除了。

    这是一个初学者的问题,所以也欢迎采用完全不同的方法。或链接到其他Q&甲

    import React from 'react';
    
    var ForumList = React.createClass({
      render: function() {
          return <div className="section-inner">
            {this.state.data.map(this.eachBox)}
          </div>
      },
      eachBox: function(box, i) {
        return <div key={i} className="box-door">
            <div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
              {box.id}
            </div>
        </div>
      },
      getInitialState: function() {
        return {data: [
          {
            id: 47,
            is_collected: false
          },
          {
            id: 23,
            is_collected: false
          },
          {
            id: 5,
            is_collected: true
          }
        ]};
      },
      clickCollect: function(index) {
        var data = this.state.data.slice(0);
        data[index].is_collected = !data[index].is_collected;
        this.setState({data: data});
      }
    });
    
    module.exports = ForumList;
    

4 个答案:

答案 0 :(得分:3)

我个人并不总是遵循这个规则,如果你真的明白你想要做什么,那么我不认为这是一个问题。

var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});

在这种情况下,改变状态并再次调用setState就好了

this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});

你应该避免改变你的状态的原因是,如果你引用了this.state.data并多次调用setState,你可能会丢失数据:

const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })

myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`

如果您真的关心这一点,请转到immutable.js

答案 1 :(得分:1)

如果您想要遵循最佳做法,则在更改任何属性时,应该对所有数组进行浅层复制。请查看“不可变”库实现。

但是,根据我的经验,从我的观点来看,如果您有“shouldCompomenentUpdate”实现,则应调用setState方法。如果你认为,你的浅拷贝将消耗更多的资源,然后对虚拟dom检查作出反应,你可以这样做:

this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();

答案 2 :(得分:0)

使状态静音直接打破了React数据流的主要原则(使其成为单向),使您的应用程序非常脆弱,基本上忽略了整个组件的生命周期。

所以,虽然没有什么能阻止你在没有setState({})的情况下改变组件状态,但如果你想真正利用React,你将不得不不惜一切代价避免这种情况,否则你将跳过其中一个库的核心功能。

答案 3 :(得分:0)

如果我理解你的问题,你就有一个对象数组,当数组中单个对象的属性发生变化时,

  
      
  1. 创建数组的深层克隆并传递给setState
  2.   
  3. 创建浅层克隆并传递给setState
  4.   

我刚刚使用redux sample todo app进行了检查,如果对象的单个属性发生更改,则需要创建该单个对象的新副本,而不是整个数组。我建议您阅读redux,如果可能,请使用管理应用的状态。