React useEffect加载数据后修改数组报错问题及修复
问题原因与解决方案
问题根源
你遇到的是闭包陷阱:组件首次渲染时创建的setTimeout,内部捕获的是初始状态的posts(空数组[])。即便后续useEffect成功拉取数据并更新了posts状态,这个定时器闭包里的posts依然是初始的空数组,所以6秒后执行时posts.length为0,访问allPosts[10]就会触发越界错误。
解决方案1:使用函数式更新状态
React的setState支持传入函数,函数参数是当前最新的状态值,能确保拿到实时的posts数据:
import { useEffect, useState } from "react"; export default function Posts() { const [posts, setPosts] = useState([]); useEffect(() => { fetch('https://jsonplaceholder.typicode.com/posts') .then((response) => response.json()) .then((json) => { setPosts(json); }); }, []); setTimeout(() => { // 函数式更新,获取最新状态 setPosts(prevPosts => { const updatedPosts = [...prevPosts]; // 增加长度判断,避免越界 if (updatedPosts.length > 10) { updatedPosts[10].title = "a new title"; } return updatedPosts; }); }, 6000); return ( <div> {posts.map((post) => <div key={post.id}>{post.id}: <input type='text' readOnly value={post.title} /></div>)} </div> ); }
解决方案2:将定时器放入useEffect并清理(更严谨)
把setTimeout移到useEffect内部,同时在组件卸载时清除定时器避免内存泄漏,结合函数式更新确保状态准确:
import { useEffect, useState } from "react"; export default function Posts() { const [posts, setPosts] = useState([]); useEffect(() => { fetch('https://jsonplaceholder.typicode.com/posts') .then((response) => response.json()) .then((json) => { setPosts(json); }); // 创建定时器 const timer = setTimeout(() => { setPosts(prevPosts => { const updatedPosts = [...prevPosts]; if (updatedPosts.length > 10) { updatedPosts[10].title = "a new title"; } return updatedPosts; }); }, 6000); // 组件卸载时清除定时器 return () => clearTimeout(timer); }, []); return ( <div> {posts.map((post) => <div key={post.id}>{post.id}: <input type='text' readOnly value={post.title} /></div>)} </div> ); }
关键注意点
- 增加
updatedPosts.length > 10的判断,避免数据加载延迟时依然触发越界错误。 - 函数式更新是解决此类闭包问题的标准方案,不受组件渲染周期中闭包捕获旧值的影响。
内容的提问来源于stack exchange,提问作者James Hatton
相关产品推荐
相关产品推荐

