useState 设置方法没有立即反映变化
- 2024-11-02 21:00:00
- admin 原创
- 33
问题描述:
我正在尝试学习 hooks,但该useState
方法让我感到困惑。我正在以数组的形式为状态分配初始值。useState
无论是否使用扩展语法,set 方法对我来说都不起作用。
我在另一台电脑上创建了一个 API,我正在调用它并获取我想要设置到状态中的数据。
这是我的代码:
<div id="root"></div>
<script type="text/babel" defer>
// import React, { useState, useEffect } from "react";
// import ReactDOM from "react-dom";
const { useState, useEffect } = React; // web-browser variant
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 =", result);
setMovies(result);
console.log("movies =", movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
</script>
<script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script>
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
运行代码片段Hide results展开片段
都setMovies(result)
不起作用setMovies(...result)
。
我希望result
将变量推入movies
数组中。
解决方案 1:
与通过扩展或创建的类组件非常相似.setState()
,使用钩子提供的更新程序的状态更新也是异步的,并且不会立即反映出来。React.Component
`React.PureComponent`useState
此外,这里的主要问题不仅仅是异步性,还在于函数根据其当前闭包使用状态值,并且状态更新将反映在下一次重新渲染中,现有闭包不受影响,但会创建新的闭包。现在在当前状态下,钩子中的值由现有闭包获取,当重新渲染发生时,闭包将根据函数是否再次重新创建进行更新。
即使你添加了一个setTimeout
函数,虽然重新渲染发生之后的一段时间内会运行超时,但它setTimeout
仍然会使用先前闭包中的值,而不是更新的值。
setMovies(result);
console.log(movies) // movies here will not be updated
如果要在状态更新时执行操作,则需要使用useEffect
钩子,就像componentDidUpdate
在类组件中使用一样,因为返回的 setteruseState
没有回调模式
useEffect(() => {
// action on update of movies
}, [movies]);
就更新状态的语法而言,setMovies(result)
将movies
用异步请求中可用的值替换状态中的先前值。
但是,如果要将响应与先前存在的值合并,则必须使用状态更新的回调语法以及正确使用扩展语法,例如
setMovies(prevMovies => ([...prevMovies, ...result]));
解决方案 2:
上一个答案的附加详细信息:
虽然 ReactsetState
是异步的(类和钩子都是),并且很容易用这个事实来解释观察到的行为,但这并不是发生这种情况的原因。
TLDR:原因在于围绕不可变值的闭包const
范围。
解决方案:
读取渲染函数中的值(不在嵌套函数内):
useEffect(() => { setMovies(result) }, [])
console.log(movies)
将变量添加到依赖项中(并使用react-hooks/exhaustive-deps eslint 规则):
useEffect(() => { setMovies(result) }, [])
useEffect(() => { console.log(movies) }, [movies])
使用临时变量:
useEffect(() => {
const newMovies = result
console.log(newMovies)
setMovies(newMovies)
}, [])
使用可变引用(如果我们不需要状态而只想记住值 - 更新引用不会触发重新渲染):
const moviesRef = useRef(initialValue)
useEffect(() => {
moviesRef.current = result
console.log(moviesRef.current)
}, [])
解释为什么会发生这种情况:
如果异步是唯一的原因,那么这是可能的await setState()
。
然而,在 1 次渲染期间,props
和都state
被假定为保持不变。
将
this.state
其视为不可变的。
使用钩子,可以通过使用带有关键字的常量值来增强此假设const
:
const [state, setState] = useState('initial')
两次渲染之间的值可能不同,但在渲染本身内部以及任何闭包(渲染完成后仍存活更长时间的函数,例如useEffect
,事件处理程序、任何 Promise 或 setTimeout 内)内部保持不变。
考虑以下虚假的但是同步的、类似 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>
运行代码片段Hide results展开片段
解决方案 3:
我知道已经有很好的答案了。但我想给出另一个解决同一问题的想法,并使用我的模块react-useStateRef访问最新的“电影”状态,它每周有 11,000 多次下载。
正如您所理解的,通过使用 React state,您可以在每次状态更改时渲染页面。但通过使用 React ref,您可以始终获取最新值。
因此,该模块react-useStateRef
允许您同时使用状态和引用。它向后兼容React.useState
,因此您只需替换import
语句
const { useEffect } = React
import { useState } from 'react-usestateref'
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
const result = [
{
id: "1546514491119",
},
];
console.log("result =", result);
setMovies(result);
console.log("movies =", movies.current); // will give you the latest results
} catch (e) {
console.error(e);
}
})();
}, []);
更多信息:
react-usestateref
解决方案 4:
这里的大多数答案都是关于如何根据先前的值更新状态,但我不明白这与问题有何关系
useState 设置方法没有立即反映变化
React 18
useState 是异步的:
当发生触发特定代码的事件时,代码开始运行,当它完成时,React 将检查是否存在状态更新,如果是,则仅更新钩子的值useState
,并且这会导致新的渲染,其中新值可用。
const [example,setExemple] = useState("")
//...
<button
onClick={() => {
const newValue = "new";
setExample(newValue);
console.log(example); // output "" and this is normal, because the component didn't rerenderd yet so the new value is not availabe yet
}}
>
Update state
</button>
假设我们有一个场景,其中某个状态依赖于另一个状态,例如,我们希望根据example
每次更新的新值进行 API 调用,然后将响应中的数据存储在另一个状态中anotherExample
。
为了实现这一点,我们有两种方法:
1. 使用以下值newValue
:
<button
onClick={async () => {
const newValue = "new";
const response = await axios.get(`http://127.0.0.1:5000/${newValue}`);
setExample(newValue);
setAnotherExample(response.data);
}}
>
test
</button>
因为您知道example
将会接收这个值,所以您可以直接基于它创建逻辑。
2. 每次触发useEffect运行时,example
通过在其依赖项数组中包含以下内容进行更新example
:
<button
onClick={() => {
const newValue = "new";
setExample(newValue);
}}
>
test
</button>
useEffect(() => {
async function test(){
const response = await axios.get(`http://127.0.0.1:5000/${example}`);
setAnotherExample(response.data);
}
test();
}, [example])
因此,当example
使用事件函数更新组件重新渲染时,我们现在处于一个新的不同渲染中,一旦完成,useEffect
它将运行,因为的值example
与上次渲染期间的值不同,并且由于它是一个新的不同渲染,useState 钩子的新值example
在这里可用。
注意:在第一次挂载期间,钩子useEffect
无论如何都会运行。
哪种方法更好?
虽然第一种方法将在一次渲染中完成所有工作
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件