setState 意外更新非状态属性
Posted
技术标签:
【中文标题】setState 意外更新非状态属性【英文标题】:setState unexpectedly updates non-state properties 【发布时间】:2019-01-17 03:31:05 【问题描述】:我不知道这是一个已知问题还是预期的功能,但我发现了一个有趣的问题。
所以我们都知道,如果我们想在 React 中渲染一个响应式值,我们必须将值放入 state 中,并使用 setState:
constructor()
super();
this.state = counter: 0
this.incrementButtonListener = (e) =>
e.preventDefault();
this.setState(prevState => ( counter: prevState.counter + 1 ));
;
render()
return (
<div>
<h1>this.state.counter</h1>
// When clicked, counter increments by 1 and re-renders
<button onChange=this.incrementButtonListener>Increment</button>
</div>
)
但是如果我们将counter
作为字段属性,render()只会在组件创建时捕捉counter
的快照,即使counter
递增,结果也不会反应式显示在渲染():
constructor()
super();
this.counter = 0;
this.incrementButtonListener = (e) =>
e.preventDefault();
this.counter++;
;
render()
return (
<div>
<h1>this.counter</h1>
// When clicked, counter increments by 1 but the difference is NOT rendered
<button onChange=this.incrementButtonListener>Increment</button>
</div>
)
对吗?基本的东西。
但是,当我尝试摆弄这段代码时,发生了一个有趣的事情。我们将 counter 保留为字段属性,其他所有内容均完好无损。唯一的区别是,在incrementButtonListener
中,我要在someStateProperty
上添加一个setState
:
constructor()
super();
this.counter = 0;
this.incrementButtonListener = (e) =>
e.preventDefault();
this.counter++;
/*-------------------------------ADD THIS*/
this.setState();
// You have to pass an object, even if it's empty. this.setState() won't work.
/*-----------------------------------------*/
;
render()
return (
<div>
<h1>this.counter</h1>
// Surprise surprise, now this.counter will update as if it was in the state!
<button onChange=this.incrementButtonListener>Increment</button>
</div>
)
这一次,this.counter 更新为状态!
所以我的假设是,每次调用 setState(甚至使用空对象作为参数)时,render() 都会再次运行,this.counter
将被重新计算并因此递增。当然,它不会像状态属性那样 100% 具有反应性。但是,在这个用例中,this.counter
唯一会改变的时间是我点击了Increment
按钮。因此,如果我在侦听器中放置一个 setState,它会像 this.counter
一样工作。
现在,我不确定这是可接受的行为还是只是意外的黑客行为,以及我是否应该使用它。谁能帮我详细说明一下?
如果您想查看实际行为,请查看fiddle。您可以注释掉第 7 行中的this.setState()
位以查看区别。
【问题讨论】:
***.com/questions/24718709/… 坦率地说,我没有看到 unexpected 行为。 React 只是确保(默认情况下)在每次setState
调用之后调用 render
。如果您在调用setState
的同时更改成员属性,那么render
输出更新 属性的值(因为它在setState
之前更改)就不足为奇了。
您可以使用 this.forceUpdate();
强制渲染 - 无需通过 setState 调用它。这是(嗯?)已知的可能性,但被视为反模式。
【参考方案1】:
因为您没有拦截状态更改,所以它会导致重新渲染,这反过来又会导致您使用递增的实例属性。这是设计使然。对 React 状态的任何更改都会导致组件重新渲染,除非您使用生命周期挂钩来控制是否应该发生这种情况。
见https://reactjs.org/docs/react-component.html#shouldcomponentupdate
【讨论】:
【参考方案2】:React 使用 this.state
和 setState
因为它有自己的用途。
通过使用 React 状态机制,我们获得了冗长、类函数式编程风格等优点,该风格应该是纯函数并且不会改变数据。
每次调用 setState
时,它都会使用新值而不是改变现有值并使行为不可预测。
您还可以通过使用shouldComponentUpdate
检查组件内部的道具/状态来防止组件重新渲染或更新。
在您的应用程序变得复杂之后,像 Redux
这样的库可以在未来拯救您。对于更简单的组件或应用,React 状态就足够了。
进一步阅读:
https://reactjs.org/docs/faq-state.html
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
https://spin.atomicobject.com/2017/06/07/react-state-vs-redux-state/
【讨论】:
【参考方案3】:您可以在 forcrUpdate 上替换 setState。 如果你的团队使用装饰风格的代码,那么永远不要使用 setState() 或 forceUpdate()。 React 的作者建议制作像纯函数一样的组件。
【讨论】:
【参考方案4】:class Hello extends React.Component
state = counter: 0 ;
increment = () =>
const counter = this.state;
this.setState( counter: counter + 1 );
;
decrement = () =>
const counter = this.state;
this.setState( counter: counter - 1 );
;
render()
const counter = this.state;
return (
<div>
<h1>counter</h1>
<p>
<button onClick=this.increment>Increment</button>
</p>
<p>
<button onClick=this.decrement>Decrement</button>
</p>
</div>
);
ReactDOM.render(<Hello />, document.getElementById("container"));
我希望这可以帮助您做出反应!如果您有任何问题,请告诉我:)
【讨论】:
以上是关于setState 意外更新非状态属性的主要内容,如果未能解决你的问题,请参考以下文章
React Stateful Class Component:使用“this.setState”更新状态的属性,不起作用。没有错误。状态不变
反应:在构造函数上绑定方法时,在 setState 内实现计时器,状态属性未定义
ReactJS TypeError:无法读取未定义的属性“setState”[重复]