React二维数组状态不更新问题:点击网格按钮后grid状态始终输出空数组
React二维数组状态不更新问题:点击网格按钮后grid状态始终输出空数组
嘿,这个问题我碰到过好几次了,核心原因是闭包陷阱在搞鬼,我给你拆解清楚:
问题根源
你在createGrid函数里创建单元格的onClick事件时,绑定的是当时的reveal函数。而这个reveal函数捕获的是它创建瞬间的grid状态——也就是初始的空数组[]。
为什么会这样?因为React的setState是异步操作:当你在useEffect里调用createGrid并执行setGrid([...arr])后,grid并不会立刻更新。这时候生成的按钮点击事件,引用的还是旧的reveal闭包,里面的grid始终停留在初始值。哪怕之后grid真的更新了,按钮的点击事件也不会自动拿到最新状态。
几种实用解决办法
我给你推荐三个简单有效的方案,按需选择:
方案1:用useRef保存最新的grid状态
useRef的值是可变的,而且不会触发组件重新渲染,能始终拿到最新的状态,是解决这类闭包问题的常用方案:
const App = () => { const [valid, setValid] = React.useState(false); const [grid, setGrid] = React.useState([]); const [input, setInput] = React.useState(null); // 用ref同步最新的grid状态 const gridRef = React.useRef(grid); // 每次grid更新时,同步到ref里 React.useEffect(() => { gridRef.current = grid; }, [grid]); const reveal = () => { console.log("grid is ", gridRef.current); // 从ref中获取最新值 }; const createGrid = () => { if (input !== "3") return []; const arr = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => ({ onClick: () => reveal(), })) ); setGrid([...arr]); }; React.useEffect(() => { setValid(input == "3"); createGrid(); }, [input]); return ( <div> <Input setInput={setInput} /> {valid && <Grid grid={grid} />} </div> ); };
方案2:点击时直接传递当前grid到reveal
修改Grid组件和点击事件逻辑,把当前的grid作为参数直接传入,彻底绕开闭包问题:
// 修改Grid组件,让点击事件传递当前grid const Grid = ({ grid }) => grid.flat().map((cell, index) => ( <button key={index} onClick={() => cell.onClick(grid, index)}> {index} </button> )); // 修改App里的reveal和createGrid const reveal = (currentGrid) => { console.log("grid is ", currentGrid); }; const createGrid = () => { if (input !== "3") return []; const arr = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => ({ onClick: (grid) => reveal(grid), })) ); setGrid([...arr]); };
方案3:用useCallback包裹reveal并依赖grid
这个方案逻辑更直观,但每次grid更新时会重新创建整个网格数组,小网格场景下完全没问题:
const App = () => { const [valid, setValid] = React.useState(false); const [grid, setGrid] = React.useState([]); const [input, setInput] = React.useState(null); // 用useCallback包裹reveal,依赖grid保证拿到最新值 const reveal = React.useCallback(() => { console.log("grid is ", grid); }, [grid]); // 同样用useCallback包裹createGrid,依赖reveal确保点击事件用最新的reveal const createGrid = React.useCallback(() => { if (input !== "3") return []; const arr = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => ({ onClick: () => reveal(), })) ); setGrid([...arr]); }, [input, reveal]); React.useEffect(() => { setValid(input == "3"); createGrid(); }, [input, createGrid]); return ( <div> <Input setInput={setInput} /> {valid && <Grid grid={grid} />} </div> ); };
总结
最推荐方案1,用useRef保存最新状态,既不会额外触发组件渲染,也能稳定拿到最新的grid值。方案2也很简洁,直接传递当前状态,彻底避免闭包陷阱。
备注:内容来源于stack exchange,提问作者K Split X
相关产品推荐
相关产品推荐

