[英]Why does calling react setState method not mutate the state immediately?
我正在阅读reactjs文档的Forms部分,并尝试使用此代码来演示onChange
用法( JSBIN )。
var React= require('react');
var ControlledForm= React.createClass({
getInitialState: function() {
return {
value: "initial value"
};
},
handleChange: function(event) {
console.log(this.state.value);
this.setState({value: event.target.value});
console.log(this.state.value);
},
render: function() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange}/>
);
}
});
React.render(
<ControlledForm/>,
document.getElementById('mount')
);
当我在浏览器中更新<input/>
值时, handleChange
回调中的第二个console.log
打印与第一个console.log
相同的value
,为什么我看不到this.setState({value: event.target.value})
在handleChange
回调的scope中?
来自 React 的文档:
setState()
不会立即改变this.state
而是创建一个挂起的 state 转换。 在调用此方法后访问this.state
可能会返回现有值。 无法保证对setState
的调用同步操作,并且可能会批处理调用以提高性能。
如果您希望在 state 更改发生后执行 function,请将其作为回调传递。
this.setState({value: event.target.value}, function () { console.log(this.state.value); });
如 React 文档中所述,不能保证setState
会同步触发,因此您的console.log
可能会在更新之前返回 state。
Michael Parker 提到在setState
中传递回调。 state 更改后处理逻辑的另一种方法是通过componentDidUpdate
生命周期方法,这是 React 文档中推荐的方法。
通常我们建议使用 componentDidUpdate() 来代替这种逻辑。
这在可能连续触发setState
时特别有用,并且您希望在每次 state 更改后触发相同的 function。 您可以将 function 放置在componentDidUpdate
内部,而不是为每个setState
添加回调,必要时在内部使用特定逻辑。
// example componentDidUpdate(prevProps, prevState) { if (this.state.value > prevState.value) { this.foo(); } }
你可以尝试使用 ES7 async/await。 例如使用您的示例:
handleChange: async function(event) { console.log(this.state.value); await this.setState({value: event.target.value}); console.log(this.state.value); }
注意 react 生命周期方法!
我工作了几个小时才发现每次setState()
都会调用getDerivedStateFromProps
。
😂
async-await
syntax works perfectly for something like the following...
changeStateFunction = () => {
// Some Worker..
this.setState((prevState) => ({
year: funcHandleYear(),
month: funcHandleMonth()
}));
goNextMonth = async () => {
await this.changeStateFunction();
const history = createBrowserHistory();
history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}
goPrevMonth = async () => {
await this.changeStateFunction();
const history = createBrowserHistory();
history.push(`/calendar?year=${this.state.year}&month=${this.state.month}`);
}
由于setState
的异步特性,在调用setState
方法后访问this.state
不能保证返回更新的状态。
为了保证调用setState
后的更新,您可以采用两种解决方案。
解决方案1:如上述答案之一所述,将您的代码放入componentDidUpdate
方法中
解决方案 2:如上述另一个答案中所述,将您的内容作为回调传递
this.setState({value: myValue}, function () { this.functionThatIsExecutedWhenStateIsUpdated(); });
需要注意的是,这两种解决方案显然不能互换。 一个不能轻易解决另一个的所有用例。 作为一般规则,如果可以,最佳实践表明解决方案 1 是首选。 但是,在某些用例中,只有解决方案 2“更有效”有效,例如“update-my-view-and-post-my-data”用例。 这个用例是这样的:
添加项目后,比如“添加计划”,我想将该项目添加到前端列表并立即将刚刚更新的列表发布到后端,如下面的概念所示:
如果你不做任何一个解决方案,即如果你只在你的代码中这样说:
addToItemArray = () => { this.setState{{ scheduledItemsArray: newObjectListWithMax}} this.postData(); } <button className="btn btn-secondary btn-block" onClick={this.addToItemArray}>Add Shedule</button>
...您将发布不包括“Delivery to Max”项目的列表,因为当您this.postData()
时 state 不会更新(同样,因为它是异步的)。
如果您使用解决方案 1,您将在 Schedule Name 文本框中输入每个字符后进行 POST
还有其他方法可以满足此用例,但解决方案 2 在阅读代码时最能传达意图。
鉴于此用例在几乎每个 web 应用程序中无处不在, Michael 的回答所解释的回调技术是每个开发人员工具包中不可或缺的一段代码。
state 有时会出现此问题。
如果是钩子,你应该使用useEffect
钩子,如下 -
const [fruit, setFruit] = useState(''); setFruit('Apple'); useEffect(() => { console.log('Fruit', fruit); }, [fruit])
这拯救了我的一天,希望会帮助你
简单地说 - this.setState({data: value}) 本质上是异步的,这意味着它会移出调用堆栈,除非它被解决,否则只会返回调用堆栈。
请阅读有关事件循环以清楚了解 JS 中的异步性质以及为什么需要时间来更新 -
https://medium.com/front-end-weekly/javascript-event-loop-explained-4cd26af121d4
因此 -
this.setState({data:value}); console.log(this.state.data); // will give undefined or unupdated value
因为更新需要时间。 实现上述过程——
this.setState({data:value},function () { console.log(this.state.data); });
响应不同的集合 state 调用,以便它可以确定重新呈现网站的最佳策略是什么。
想象一下,您有一个应用程序,其中包含许多不同的组件。 也许,只需单击一下按钮,您就可以在多个组件中更新 state,而不仅仅是当前组件。 在这种情况下,React 不希望完全隔离并独立完成所有这些不同的更新。
React 想弄清楚它是否可以将所有这些更新堆叠在一起,也许有一种更优化的方式来更新这些组件,从而提高性能。 这就是 React 在幕后所做的。 结果,设置state调用为异步调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.