React实现Wordle克隆:兄弟组件如何监听全局状态变化
问题根因
状态无法同步渲染是三个核心问题导致的:
- 全局状态没有使用React响应式状态API:直接把普通数组传给Context Provider,直接修改原数组不会触发React重渲染检测,所有消费Context的组件都收不到更新通知。
- 输入位置追踪变量失效:
currentRow和currentTile是Keypad组件内的普通局部变量,每次组件重渲染都会被重置为0,既无法持久化记录当前输入位置,也无法被其他组件共享。 - Row组件硬编码只渲染第一行:代码写死取
rowData[0],后续行的输入永远不会被展示,也没有实现全网格遍历渲染逻辑。
修复方案
1. 重构AppContext.js,用响应式状态管理全局数据
把网格数据、当前输入位置都放到Context的状态里,同时暴露状态更新方法,确保所有修改都能触发组件重渲染:
// AppContext.js import { createContext, useState } from "react"; // 初始化6行5列的空网格 const initGuessRows = Array(6).fill(null).map(() => Array(5).fill('')); export const AppContext = createContext(); const AppContextProvider = (props) => { // 网格数据状态 const [guessRows, setGuessRows] = useState(initGuessRows); // 当前输入位置状态:记录当前激活的行、格子索引 const [position, setPosition] = useState({ currentRow: 0, currentTile: 0 }); // 全局输入字母方法 const addLetter = (letter) => { const { currentRow, currentTile } = position; // 边界判断:行/格子超出网格范围直接返回 if (currentRow >= 6 || currentTile >= 5) return; // 拷贝新数组,禁止直接修改原状态 const newRows = [...guessRows]; newRows[currentRow] = [...newRows[currentRow]]; newRows[currentRow][currentTile] = letter; // 更新网格状态 setGuessRows(newRows); // 输入位置后移一位 setPosition(prev => ({...prev, currentTile: prev.currentTile + 1})); }; return( <AppContext.Provider value={{ guessRows, position, addLetter }}> {props.children} </AppContext.Provider> ) } export default AppContextProvider;
2. 修改Keypad.js,调用全局方法更新状态
移除组件内私有的位置变量和直接修改数组的逻辑,从Context获取全局方法处理点击事件:
// Keypad.js import { useContext } from "react" import { AppContext } from "../AppContext" const Keypad = () => { const { addLetter } = useContext(AppContext); const letters = [ "Q","W","E","R","T","Y","U","I","O","P", "A","S","D","F","G","H","J","K","L", "Z","X","C","V","B","N","M" ]; return ( <div className="keyboard-container"> {letters.map((letter, index) => { return ( <div className="key" key={letter} onClick={() => addLetter(letter)}> {letter} </div> ) })} {/* 后续可在此添加删除、提交按键,对应新增Context方法即可 */} </div> ) }; export default Keypad;
3. 修改Row.js,通过props接收行索引渲染对应行
移除硬编码的第一行逻辑,让Row组件接收rowIndex参数,渲染对应行的格子数据:
// Row.js import { useContext } from "react"; import { AppContext } from "../AppContext"; const Row = ({ rowIndex }) => { const { guessRows } = useContext(AppContext); const rowData = guessRows[rowIndex]; return ( <div className="row"> {rowData.map((letter,index) => { return( <div className="tile" key={index} id={`row-${rowIndex}-tile-${index}`}>{letter}</div> ) })} </div> ) } export default Row;
4. 补充Grid.js遍历渲染逻辑
Grid组件负责遍历6行网格,给每个Row传递对应的行索引:
// Grid.js import Row from "./Row"; const Grid = () => { return ( <div className="grid-container"> {Array(6).fill(null).map((_, index) => <Row key={index} rowIndex={index} />)} </div> ) } export default Grid;
5. 检查根组件配置
确保入口文件(通常是index.js)中用AppContextProvider包裹App组件,否则所有组件都无法访问Context数据:
// index.js import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import AppContextProvider from './AppContext'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <AppContextProvider> <App /> </AppContextProvider> </React.StrictMode> );
最后在App.js中引入Grid、Keypad组件调整布局即可。
注意事项
- 永远不要直接修改React状态/Context里的对象、数组,必须创建新的拷贝再更新,否则React无法检测到状态变化。
- 跨组件共享的状态和变量,必须放到全局状态或者共同的父组件里,不要在单个子组件里用普通变量存储公共逻辑依赖的数据,否则既无法持久化也无法跨组件共享。
- 渲染列表时要加唯一的
key属性,动态增删的列表不要用遍历索引当key,本项目固定6行网格用索引不会出现异常。
内容的提问来源于stack exchange,提问作者Josh Simon
相关产品推荐
相关产品推荐

