React中useState更新函数在回调内无法更新状态问题
问题根源分析
你的核心问题是把选中行的状态放在了单个ScheduleGridBodyRow组件内部,而每个ScheduleGridBodyRow都是独立的组件实例,各自维护自己的selectedRow状态。这就导致:
- 点击行A时,只有行A组件的
selectedRow会被设置,但行B、C等其他行组件的selectedRow依然是初始的undefined - 当你点击行B时,行B的回调里读取的是自己组件的
selectedRow(永远是undefined),所以会执行“设置选中”的逻辑,但行A的状态不会被重置,最终导致多行同时处于选中状态,且你永远看不到其他行的状态值
另外,直接存储DOM元素到React状态里本身就不推荐,DOM元素是易变的,可能会导致意外的渲染问题,更合理的方式是存储选中行的唯一标识(比如game.id)。
解决方案:状态提升到父组件
将选中行的状态(比如选中的gameId)提升到所有ScheduleGridBodyRow的父组件中,由父组件统一管理选中状态,然后通过props传递给每个子组件,子组件根据父组件传递的选中ID来判断自己是否需要高亮。
1. 父组件修改(示例)
// 假设父组件是ScheduleGrid function ScheduleGrid() { const [selectedGameId, setSelectedGameId] = useState<number | undefined>(); const [selectedGameData, setSelectedGameData] = useState<any>(); const weekData = [...]; // 你的周数据 // 父组件统一处理选中逻辑 const handleRowClick = (gameId: number, rowElement: HTMLTableRowElement) => { // 先重置之前选中的行样式 if (selectedGameId !== undefined) { const prevRow = document.getElementById(String(selectedGameId)); if (prevRow) { prevRow.style.setProperty('--bs-table-bg', '#212529'); } } if (selectedGameId === gameId) { // 点击已经选中的行,取消选中 setSelectedGameId(undefined); setSelectedGameData(undefined); } else { // 设置新选中行样式 rowElement.style.setProperty('--bs-table-bg', '#4d5154'); setSelectedGameId(gameId); const selectedGame = weekData.filter(game => game.id === gameId)[0]; setSelectedGameData(selectedGame); } }; return ( <table> <tbody> {weekData.map(game => ( <ScheduleGridBodyRow key={game.id} game={game} selectedGameId={selectedGameId} onRowClick={handleRowClick} /> ))} </tbody> </table> ); }
2. 子组件ScheduleGridBodyRow修改
import React, {MouseEvent, useCallback, useEffect} from 'react'; import './App.css'; import {scheduleGridColumnDefs, sequenceGenerate} from "./util"; import ScheduleGridBodyRowCol from "./ScheduleGridBodyRowCol"; function ScheduleGridBodyRow( props: { game: any, selectedGameId: number | undefined, onRowClick: (gameId: number, rowElement: HTMLTableRowElement) => void } ) { const {game, selectedGameId, onRowClick} = props; const onClick = useCallback((event: MouseEvent<HTMLTableRowElement>) => { onRowClick(game.id, event.currentTarget); }, [game.id, onRowClick]); // 组件挂载/选中状态变化时同步样式 useEffect(() => { const rowElement = document.getElementById(String(game.id)); if (rowElement) { rowElement.style.setProperty( '--bs-table-bg', selectedGameId === game.id ? '#4d5154' : '#212529' ); } }, [game.id, selectedGameId]); return ( <tr id={game.id} data-game-id={game.id} onClick={onClick}> {scheduleGridColumnDefs.map((columnDef: any) => ( <ScheduleGridBodyRowCol key={sequenceGenerate()} game={game} columnDef={columnDef}/> ))} </tr> ); } export default React.memo(ScheduleGridBodyRow);
为什么之前的方法无效?
- 每个
ScheduleGridBodyRow是独立实例,各自的selectedRow状态完全隔离,无法感知其他行的选中状态 - 直接存储DOM元素到状态中,React的状态更新是异步的,你在回调中立刻读取
selectedRow拿到的还是更新前的值 - 函数式更新里的
prevSelectedRow也只是当前组件实例的状态历史,和其他组件无关
内容的提问来源于stack exchange,提问作者GDub
相关产品推荐
相关产品推荐

