React - 不使用setState改变状态: 必须避免吗?

13
我有一段可运行的代码,但我有一个最佳实践的问题:我的状态中有一个对象数组,并且用户交互将逐个更改一个对象的值。据我所知,不应直接更改状态,而应始终使用 setState。如果我想要避免这种方式,我将通过迭代深克隆数组,然后更改克隆来达到目的。然后将状态设置为克隆。在我看来,避免更改稍后仍要更改的状态只会降低我的性能。
详细版本: this.state.data 是一个对象数组,表示论坛中主题的列表,Favorite按钮将切换,并调用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] : This makes a shallow clone of the array and ensures that any modifications to data will not affect this.state.data.
var data = []; 
for (var i in this.state.data) {
    data.push(this.state.data[i]); 
}
然后我更改索引处的值,将其设置为True(当其为False时)或False(当其为True时):
data[index].is_collected = !data[index].is_collected;
并更改状态:
this.setState({data: data});
考虑到我的数组可能很大或非常大,我猜测这个迭代会降低我的应用程序性能。如果我知道它是正确的方式,我愿意支付这个代价。但是,在这个函数(clickCollect)中,我总是将新值设置为状态,我不等待一个错误的API响应,告诉我停止进行更改。在所有情况下,新值都会进入状态。实际上,我只调用setState让UI重新渲染。所以问题是:

  1. 在这种情况下我是否需要创建深度克隆?(for var i in ...)
  2. 如果不需要,假设我的数组包含对象,使用浅拷贝(.slice(0))是否有意义?更改是在数组内部的对象上进行的,因此浅克隆仍会更改我的状态,就像副本(data = this.state.data)一样。

为简单起见删除了我的代码和API调用。

这是初学者的问题,因此完全不同的方法也可以接受。或者给出其他问答链接。

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;

感谢大家的回答。实际上我无法“接受”其中一个,因为所有四个回答都从不同的角度阐明了问题。然而,@CodinCat 因为他的回答表述得非常清晰而获得采纳。特别感谢 VJAI、Kelvin De Moya 和 degr。 - Szalai Laci
4个回答

5

个人而言,我并不总是遵循规则,如果你真正理解自己想要做的事情,我认为这并不是问题。

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

。它与IT技术有关。


3

直接改变状态会破坏React数据流的主要原则(旨在单向),使您的应用程序非常脆弱,并基本忽略整个组件生命周期。

因此,虽然没有什么可以阻止您在不使用setState({})的情况下更改组件状态,但如果您想真正利用React,则必须尽一切努力避免这种情况,否则您将跳过库的核心功能之一。


1
如果您想遵循React最佳实践,当您更改任何属性时,应该对所有数组进行浅复制。请查看“immutable”库的实现。
但是,根据我的经验和观点,如果您有“shouldCompomenentUpdate”实现,则应调用setState方法。如果您认为您的浅复制将消耗比React虚拟DOM检查更多的资源,则可以这样做:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();

0

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

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

我刚刚检查了使用redux示例todo应用程序,如果对象的单个属性发生更改,则必须创建该单个对象的全新副本而不是整个数组。我建议你阅读关于redux的相关内容,并尽可能使用它来管理你的应用程序状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接