React State更新问题:setInterval定时器内更新state值不正确
问题修复方案
根因分析
你碰到的是React Hooks的闭包陷阱问题:
- 你给useEffect传入了空依赖数组
[],因此useEffect只会在组件首次挂载时执行一次,内部创建的setInterval回调捕获到的role变量永远是首次渲染时的初始值roles[0] - 第一次定时器执行时虽然更新了
role状态,但回调函数闭包里的role始终是旧的初始值,第二次执行时计算得到的prevRoleIndex永远是0,无法正确匹配当前最新的角色状态,自然就没法正常更新。 - 额外隐患:你在切换角色前会把
role设为随机字符串,后续用findIndex匹配roles数组时,完全可能出现随机字符串和roles中某一项重合的极端情况,导致索引计算完全错误。
修复方案
我们可以用useRef存储当前最新的角色索引,绕开闭包陷阱,同时避免每次匹配索引的性能损耗和异常风险:
import { useState, useEffect, useRef } from 'react' const roles = ['Web Developer & UX Designer', 'Software Developer / Engineer', 'Blockchain Developer', 'Data Anaylist/ Scientist'] export default function App() { const [role, setRole] = useState(roles[0]) // 用ref存储当前最新的角色索引,不受闭包影响 const currentRoleIndexRef = useRef(0) const sleep = (value) => { return new Promise(r => setTimeout(r, value)); } const getRandomString = () => { return (Math.random()).toString(36).substring(2); } useEffect(() => { const timer = setInterval(async () => { console.log("Timer has started") // 直接从ref取最新的索引,不需要再从role反查 const prevRoleIndex = currentRoleIndexRef.current console.log("Previous Role State: " + roles[prevRoleIndex]) for (let i = 0; i < 10; i++) { setRole(getRandomString()) await sleep(50) } // 计算下一个索引,超出长度就回到0实现循环 const nextRoleIndex = (prevRoleIndex + 1) % roles.length const newRole = roles[nextRoleIndex] // 更新ref里的索引值 currentRoleIndexRef.current = nextRoleIndex console.log("New Role State: " + newRole) setRole(newRole) }, 3000); return () => clearInterval(timer); }, []); return ( <div> <h1>{role}</h1> </div> ) }
修复说明
- 用
useRef存储当前角色索引,ref的取值和修改不受闭包影响,每次都能拿到最新值 - 索引切换用取模运算
% roles.length实现循环播放,写法更简洁 - 不需要再从当前
role反查索引,避免了随机字符串匹配错误的问题
内容的提问来源于stack exchange,提问作者kindacoder
相关产品推荐
相关产品推荐

