React - 在不使用 setState 的情况下更改状态:必须避免吗?
Posted
技术标签:
【中文标题】React - 在不使用 setState 的情况下更改状态:必须避免吗?【英文标题】:React - Changing the state without using setState: Must avoid it? 【发布时间】:2017-06-22 23:21:17 【问题描述】:我的代码有效,但我有一个最佳实践问题:我有一组处于状态的对象,用户交互一次将更改一个对象的值。据我所知,我不应该直接更改状态,我应该始终使用 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() 等会直接改变状态。 data
和 this.state.data
都会受到影响。
var data = this.state.data.slice(0)
:这会进行浅克隆,push 和 shift 不会改变状态,但在我的克隆中,我仍然有指向状态数组元素的指针。因此,如果我更改 data[0].is_collected
,this.state.data[0].is_collected
也会更改。这发生在我打电话给setState()
之前。
通常我应该这样做:
var data = []; for (var i in this.state.data) data.push(this.state.data[i]);
然后我更改索引处的值,当它为假时设置为真,当它为真时设置为假:
data[index].is_collected = !data[index].is_collected;
并改变状态:
this.setState(data: data);
考虑到我的数组比较大或者非常大,我猜这个迭代会降低我的APP的性能。如果我出于任何原因知道这是正确的方法,我会支付这笔费用。但是,在这个函数 (clickCollect
) 中,我总是将新值设置为状态,我不是在等待一个错误的 API 响应,它会说停止进行更改。在所有情况下,新值都会进入状态。实际上,我调用 setState
只是为了让 UI 再次呈现。所以问题是:
-
在这种情况下我必须创建深层克隆吗? (
for var i in ...
)
如果不是,如果我的数组包含对象,那么进行浅克隆 (.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。 【参考方案1】:就我个人而言,我并不总是遵守规则,如果您真的了解自己想要做什么,那么我认为这不是问题。
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
【讨论】:
【参考方案2】:静音状态直接破坏了 React 数据流的主要原则(被制成单向的),使您的应用非常脆弱并且基本上忽略了整个组件生命周期。
因此,虽然没有什么能真正阻止您在没有 setState() 的情况下改变组件状态,但如果您想真正利用 React,则必须不惜一切代价避免这种情况,否则您将超越库中的一个核心功能。
【讨论】:
【参考方案3】:如果您想遵循 react 最佳实践,您应该在更改任何属性时对所有数组进行浅拷贝。请查看“不可变”库的实现。
但是,根据我的经验和我的观点,如果您有“shouldCompomenentUpdate”实现,则应该调用setState
方法。如果你认为你的浅拷贝会消耗更多的资源,那么响应虚拟 dom 检查,你可以这样做:
this.state.data[0].property = !this.state.data[0].property;
this.forceUpdate();
【讨论】:
【参考方案4】:如果我理解你的问题,你有一个对象数组,当数组中单个对象的属性发生变化时,
创建数组的深层克隆并传递给 setState 创建一个浅层克隆并传递给 setState
我刚刚检查了redux
sample todo app,如果对象的单个属性发生更改,您必须创建该单个对象的新副本,而不是整个数组。我建议您阅读 redux
并在可能的情况下使用它来管理您的应用程序的状态。
【讨论】:
以上是关于React - 在不使用 setState 的情况下更改状态:必须避免吗?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 setState() 在我的情况下不能在 React 中工作?
如何避免未使用的 setState 函数?可以在没有 setter 的情况下创建 React useState 吗?
react在哪些情况下调用this.setState会导致死循环