Javascript useState set 方法不会立即反映更改
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/54069253/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
useState set method not reflecting change immediately
提问by Pranjal
I am trying to learn hooks and the useStatemethod has made me confused. I am assigning an initial value to a state in the form of an array. The set method in useStateis not working for me even with spread(...)or without spread operator.
I have made an API on another PC that I am calling and fetching the data which I want to set into the state.
我正在尝试学习钩子,但该useState方法让我感到困惑。我以数组的形式为状态分配一个初始值。useState即使使用spread(...)或 ,in 中的 set 方法对我也不起作用without spread operator。我在另一台 PC 上创建了一个 API,我正在调用它并获取我想要设置为状态的数据。
Here is my code:
这是我的代码:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const StateSelector = () => {
const initialValue = [
{
category: "",
photo: "",
description: "",
id: 0,
name: "",
rating: 0
}
];
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
//const response = await fetch(
//`http://192.168.1.164:5000/movies/display`
//);
//const json = await response.json();
//const result = json.data.result;
const result = [
{
category: "cat1",
description: "desc1",
id: "1546514491119",
name: "randomname2",
photo: null,
rating: "3"
},
{
category: "cat2",
description: "desc1",
id: "1546837819818",
name: "randomname1",
rating: "5"
}
];
console.log(result);
setMovies(result);
console.log(movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
The setMovies(result)as well as setMovies(...result)is not working. Could use some help here.
的setMovies(result)以及setMovies(...result)不工作。可以在这里使用一些帮助。
I expect the result variable to be pushed into the movies array.
我希望将结果变量推送到电影数组中。
回答by Shubham Khatri
Much like setState in Class components created by extending React.Componentor React.PureComponent, the state update using the updater provided by useStatehook is also asynchronous, and will not immediately reflect
很像继承React.Componentor创建的Class组件中的setState,React.PureComponent使用useStatehook提供的updater进行的状态更新也是异步的,不会立即反映
Also the main issue here not just the asynchronous nature but the fact that state values are used by functions based on their current closures and state updates will reflect in the next re-render by which the existing closures are not affected but new ones are created. Now in the current state the values within hooks are obtained by existing closures and when a re-render happens the closures are updated based on whether function is recreated again or not
此外,这里的主要问题不仅是异步性质,而且函数使用状态值基于其当前闭包和状态更新的事实将反映在下一次重新渲染中,现有闭包不受影响但会创建新闭包。现在在当前状态下,钩子中的值由现有的闭包获得,当重新渲染发生时,闭包会根据函数是否再次重新创建而更新
Even if you add a setTimeoutthe function, though the timeout will run after some time by which the re-render would have happened, but the setTimout will still use the value from its previous closure and not the updated one.
即使您添加了一个setTimeout函数,虽然超时将在重新渲染发生的一段时间后运行,但 setTimout 仍将使用其前一个闭包的值而不是更新的值。
setMovies(result);
console.log(movies) // movies here will not be updated
If you want to perform an action on state update, you need to use the useEffect hook, much like using componentDidUpdatein class components since the setter returned by useState doesn't have a callback pattern
如果你想对状态更新执行一个动作,你需要使用 useEffect 钩子,就像componentDidUpdate在类组件中使用一样,因为 useState 返回的 setter 没有回调模式
useEffect(() => {
// action on update of movies
}, [movies]);
As far as the syntax to update state is concerned, setMovies(result)will replace the previous moviesvalue in the state with those available from the async request
就更新状态的语法而言,setMovies(result)将movies使用异步请求中可用的值替换状态中的先前值
However if you want to merge the response with the previously existing values, you must use the callback syntax of state updation along with the correct use of spread syntax like
但是,如果要将响应与先前存在的值合并,则必须使用状态更新的回调语法以及正确使用传播语法,例如
setMovies(prevMovies => ([...prevMovies, ...result]));
回答by Aprillion
Addional details to the previous answer:
上一个答案的其他详细信息:
While React's setStateisasynchronous (both classes and hooks), and it's tempting to use that fact to explain the observed behaviour, it is not the reason whyit happens.
虽然 ReactsetState是异步的(类和钩子),并且很容易用这个事实来解释观察到的行为,但这并不是它发生的原因。
TLDR: The reason is a closurescope around an immutable constvalue.
TLDR:原因是围绕不可变值的闭包范围const。
Solutions:
解决方案:
read the value in render function (not inside nested functions):
useEffect(() => { setMovies(result) }, []) console.log(movies)add the variable into dependencies (and use the react-hooks/exhaustive-depseslint rule):
useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies])use a mutable reference (when the above is not possible):
const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])
读取渲染函数中的值(不在嵌套函数内):
useEffect(() => { setMovies(result) }, []) console.log(movies)将变量添加到依赖项中(并使用react-hooks/exhaustive-depseslint 规则):
useEffect(() => { setMovies(result) }, []) useEffect(() => { console.log(movies) }, [movies])使用可变引用(当上述不可能时):
const moviesRef = useRef(initialValue) useEffect(() => { moviesRef.current = result console.log(moviesRef.current) }, [])
Explanation why it happens:
解释为什么会发生:
If async was the only reason, it would be possible to await setState().
如果异步是唯一的原因,那么await setState().
Howerver, both propsand stateare assumed to be unchanging during 1 render.
但是,props和state都假定在 1 render 期间保持不变。
Treat
this.stateas if it were immutable.
将其
this.state视为不可变的。
With hooks, this assumption is enhanced by using constant valueswith the constkeyword:
使用钩子,通过使用带有关键字的常量值可以增强这种假设const:
const [state, setState] = useState('initial')
The value might be different between 2 renders, but remains a constant inside the render itself and inside any closures(functions that live longer even after render is finished, e.g. useEffect, event handlers, inside any Promise or setTimeout).
两次渲染之间的值可能不同,但在渲染本身和任何闭包(即使在渲染完成后寿命更长的函数,例如useEffect,任何 Promise 或 setTimeout 内的事件处理程序)内仍保持不变。
Consider following fake, but synchronous, React-like implementation:
考虑以下虚假但同步的类似 React 的实现:
// sync implementation:
let internalState
let renderAgain
const setState = (updateFn) => {
internalState = updateFn(internalState)
renderAgain()
}
const useState = (defaultState) => {
if (!internalState) {
internalState = defaultState
}
return [internalState, setState]
}
const render = (component, node) => {
const {html, handleClick} = component()
node.innerHTML = html
renderAgain = () => render(component, node)
return handleClick
}
// test:
const MyComponent = () => {
const [x, setX] = useState(1)
console.log('in render:', x) // ?
const handleClick = () => {
setX(current => current + 1)
console.log('in handler/effect/Promise/setTimeout:', x) // ? NOT updated
}
return {
html: `<button>${x}</button>`,
handleClick
}
}
const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>
回答by Nikita Malyschkin
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;
Now you should see, that your code actually doeswork. What does not work is the console.log(movies). This is because moviespoints to the old state. If you move your console.log(movies)outside of useEffect, right above the return, you will see the updated movies object.
现在您应该看到,您的代码确实有效。不起作用的是console.log(movies). 这是因为movies指向旧状态。如果您将 的console.log(movies)外部移动useEffect到返回的正上方,您将看到更新后的电影对象。

