React子函数组件useEffect无法更新Context,仅存最后Tile坐标
问题:棋盘Tile坐标无法全部存入Context
开发场景
开发带方格的游戏棋盘,单位初始位于棋盘外,游戏启动后用户可将单位拖拽至指定Tile上。需在App Context中跟踪所有Tile的X、Y坐标,以此判断单位落于哪个Tile。
预期效果
Tile组件挂载时,通过useEffect将自身位置对象添加到Context的tilesCoords数组中。
问题现象
实际Context中始终仅保留最后一个Tile的坐标。
已尝试操作
- 使用useRef包裹Context无改善;
- 为useEffect添加依赖会引发无限循环,且仍仅保留最后一个Tile坐标。
代码实现
App.js
import { Encampment } from "./components/Encampment"; import "./App.css"; import { Battlefield } from "./components/Battlefield"; import { Unit } from "./components/Unit"; import { ARMY } from "./constants"; import { useState, createContext } from "react"; export const CoordsContext = createContext<{ tilesCoords: object[]; setTilesCoords: React.Dispatch<React.SetStateAction<object[]>>; }>({ tilesCoords: [], setTilesCoords: () => {}, }); function App() { const [tilesCoords, setTilesCoords] = useState<object[]>([]); const armyColours = ["black", "white"]; let whiteArmy: React.ReactElement[] = []; let balckArmy: React.ReactElement[] = []; armyColours.forEach((colour) => { Object.entries(ARMY).forEach((entries) => { const [ObjKey, value] = entries; for (let index = 0; index < value; index++) { const name = `${ObjKey}-${colour}`; const key = `${name}_${index}`; colour === "white" ? whiteArmy.push(<Unit name={name} key={key} />) : balckArmy.push(<Unit name={name} key={key} />); } }); }); return ( <div className="App"> <CoordsContext.Provider value={{ tilesCoords, setTilesCoords }}> <Encampment units={whiteArmy} /> <Battlefield /> <Encampment units={balckArmy} /> </CoordsContext.Provider> </div> ); }
Tile.js
import { FC, useRef } from "react"; import "./styles/Tile.css"; import { TileProps } from "../types/TileProps"; import { CoordsContext } from "../App"; import React, { useContext, useEffect } from "react"; const BLUE = "#4364db"; const GREEN = "#6cbc6c"; const YELLOW = "#b8b877"; const BROWN = "#8d5a5a"; const BLACK = "black"; const Tile: FC<TileProps> = ({ terrain, isHill, children }) => { const tileRef = useRef<HTMLDivElement>(null); const getPosition = () => { if (tileRef.current) { const x = tileRef.current.offsetLeft; const y = tileRef.current.offsetTop; return [x, y]; } }; const context = useRef(useContext(CoordsContext)); const { tilesCoords, setTilesCoords } = context.current; useEffect(() => { const position = getPosition(); console.log('position', position) console.log('children', children) console.log("tilesCoords", tilesCoords); setTilesCoords([...tilesCoords, { position, key: children }]); // need to pass dependencies but in a way that doesn't cause infinite loop }, []); let backgroundColor; switch (terrain) { case "water": backgroundColor = BLUE; break; case "grass": backgroundColor = GREEN; break; case "sand": backgroundColor = YELLOW; break; case "dirt": backgroundColor = BROWN; break; case "mount": backgroundColor = BLACK; break; } const outline = isHill ? "2px solid black" : "none"; const zIndex = isHill ? 2 : 0; const tileStyle = { backgroundColor, outline, zIndex, }; return <div className="tile" style={tileStyle} ref={tileRef}></div>; }; export default Tile;
问题分析与解决方案
问题根源
- 闭包捕获旧值:
useEffect依赖为空数组,导致内部的tilesCoords始终是组件挂载时的初始空数组。每个Tile执行setTilesCoords([...tilesCoords, newItem])时,都是基于空数组添加元素,最终只会保留最后一个Tile的坐标。 - 不必要的useRef包裹Context:用
useRef包裹useContext会捕获组件挂载时的旧Context值,无法获取最新的setTilesCoords和tilesCoords。
修复方案
修改Tile组件中的Context获取和useEffect逻辑:
- 移除包裹Context的
useRef,直接通过useContext获取最新的Context值; - 使用函数式更新调用
setTilesCoords,确保基于最新的数组状态添加新元素,无需依赖tilesCoords; - 保持
useEffect依赖为[setTilesCoords],它是React稳定的函数引用,不会引发无限循环。
修改后的Tile.js关键代码
const Tile: FC<TileProps> = ({ terrain, isHill, children }) => { const tileRef = useRef<HTMLDivElement>(null); const getPosition = () => { if (tileRef.current) { const x = tileRef.current.offsetLeft; const y = tileRef.current.offsetTop; return [x, y]; } }; // 直接获取Context,无需useRef包裹 const { setTilesCoords } = useContext(CoordsContext); useEffect(() => { const position = getPosition(); if (position) { // 增加空值判断,避免添加无效坐标 // 使用函数式更新,基于最新的state数组添加元素 setTilesCoords(prevCoords => [...prevCoords, { position, key: children }]); } }, [setTilesCoords]); // ... 其余样式逻辑保持不变 };
额外优化建议
- 为
tilesCoords定义更具体的类型(而非object[]),提升类型安全性; - 确保
children作为key是唯一的,避免重复添加相同Tile的坐标; - 考虑在组件卸载时从
tilesCoords中移除对应坐标(如果需要动态增减Tile)。
内容的提问来源于stack exchange,提问作者Techoplite
相关产品推荐
相关产品推荐

