You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 22:52:52