React使用useEffect/useState请求数据延迟 需两次点击提交才显示正确结果
问题原因
你遇到的点击两次提交才返回正确结果的问题,和网络延迟无关,完全是React状态更新时序+闭包导致的逻辑错误:
- React的
setState是异步批处理的,你在handleSubmit里调用setInputCurrency、setInputAmount、setOutputCurrency之后,当前函数作用域里拿到的inputAmount、exchangeRate还是上一次渲染留存的旧值,第一次点击时用旧值计算结果,必然不对。 - 你把汇率请求逻辑放在了依赖
inputCurrency、outputCurrency的useEffect里,第一次点击提交更新货币选择后,组件要等重渲染完成才会执行effect发请求拿新汇率,等新汇率返回时,你早就用旧汇率算完结果了——相当于第一次点击只完成了"更新选中的货币类型、触发新汇率请求",第二次点击时所有状态都已经更新成最新值,才能算出正确结果。 - 现有effect还有个隐藏bug:请求回调里取
data.conversion_rates[outputCurrency]时,拿到的是effect触发当次闭包里的旧outputCurrency,哪怕请求发对了,取到的汇率也可能和你刚选的输出货币不匹配。
修复方案
调整逻辑时序,不要等状态更新后再被动触发请求、计算结果,提交时直接读取表单最新值拉取汇率,一次性更新所有状态即可,修改后的完整代码如下:
import './App.css'; import { useState } from 'react'; import { Select, Button } from '@chakra-ui/react'; function App() { const [inputCurrency, setInputCurrency] = useState('USD'); const [inputAmount, setInputAmount] = useState(1); const [outputCurrency, setOutputCurrency] = useState('HKD'); const [outputAmount, setOutputAmount] = useState(7.8491); const handleSubmit = async (e) => { e.preventDefault(); // 直接读取表单当前提交的最新值,不依赖异步更新的state const curInputCurrency = e.target.inputCurrency.value; const curInputAmount = Number(e.target.inputAmount.value); const curOutputCurrency = e.target.outputCurrency.value; try { // 用最新选中的输入货币拉取汇率 const res = await fetch(`${process.env.REACT_APP_DATA_SOURCE}${curInputCurrency}`); const data = await res.json(); const curRate = data.conversion_rates[curOutputCurrency]; // 一次性更新所有状态,用最新汇率计算结果 setInputCurrency(curInputCurrency); setInputAmount(curInputAmount); setOutputCurrency(curOutputCurrency); setOutputAmount(curInputAmount * curRate); } catch (err) { console.log('汇率获取失败:', err); } }; return ( <div className="main-body"> <form onSubmit={handleSubmit}> <h3>Set input amount:</h3> <input className="inputAmount" name="inputAmount" type="number" defaultValue={1} /> <h3>Set input currency:</h3> <Select name="inputCurrency" defaultValue="USD"> <option value="USD">USD</option> <option value="HKD">HKD</option> <option value="CNY">CNY</option> <option value="GBP">GBP</option> <option value="CAD">CAD</option> </Select> <h3>Set output currency:</h3> <Select name="outputCurrency" defaultValue="HKD"> <option value="USD">USD</option> <option value="HKD">HKD</option> <option value="CNY">CNY</option> <option value="GBP">GBP</option> <option value="CAD">CAD</option> </Select> <Button colorScheme="blue" type="submit"> Submit </Button> </form> <h3> {inputAmount} {inputCurrency} is equal to: </h3> <h3> {outputAmount} {outputCurrency} </h3> </div> ); } export default App;
核心改动说明
- 删掉了多余的
useEffect汇率请求逻辑,不再依赖状态变化触发请求,从根源避免闭包拿旧值、状态更新时序错乱的问题 - 提交时直接读取表单的最新值发请求、算结果,所有状态一次性同步更新,点击一次提交就能拿到正确结果
- 给输入框和下拉选择加了
defaultValue匹配初始状态,页面加载后表单默认值和展示结果一致 - 把原来混用
await和.then的请求写法改成了统一的async/await语法,减少链式调用的闭包隐患
如果你需要实现"切换货币时不点击提交就自动更新汇率"的效果,记得把输出金额的计算逻辑也放到
useEffect里,等汇率请求返回后再计算结果,不要在提交事件里依赖旧state做计算。
内容的提问来源于stack exchange,提问作者wongwong3000
相关产品推荐
相关产品推荐

