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

React+TypeScript角色选择器切换种族时状态滞后问题

问题:React + TypeScript角色选择器切换种族时UI滞后一步

使用React + TypeScript开发简易角色选择器,从data.json获取种族数组并存入状态,通过“Increase”和“Decrease”按钮切换种族,每个种族带有属性加成。但点击按钮时,显示的种族总是滞后一步:首次点击“下一个”/“上一个”,UI不会立即更新,需第二次点击才会变更,仅循环逻辑看似生效。

组件简化代码

type BaseAttributesType = {
  strength?: number;
  dexterity?: number;
  intelligence?: number;
  constitution?: number;
  charisma?: number;
  wisdom?: number;
};

type RacesType = {
  name: string;
  description: string;
  bonuses: BaseAttributesType;
  img: string;
};

const App = () => {
  const [baseAttributes, setBaseAttributes] = useState<BaseAttributesType | null>(null);
  const [races, setRaces] = useState<RacesType[] | null>(null);
  const [race, setRace] = useState<RacesType | null>(null);
  const [index, setIndex] = useState<number>(0);

  useEffect(() => {
    fetch("data.json")
      .then(res => res.json())
      .then(data => {
        setBaseAttributes(data.base_attributes);
        setRaces(data.races);
        setRace(data.races[0]);
      });
  }, []);

  const Increase = () => {
    if (races && index === races.length - 1) {
      setIndex(0);
    } else {
      setIndex(index + 1);
    }
    if (races) {
      setRace(races[index]);
    }
  };

  const Decrease = () => {
    if (races && index === 0) {
      setIndex(races.length - 1);
    } else {
      setIndex(index - 1);
    }
    if (races) {
      setRace(races[index]);
    }
  };

  return (
    <div className="container">
      <CharacterPreview img={race?.img || ""}></CharacterPreview>
      <div className="sor">
        <img onClick={Decrease} id="balos" src="img/arrow.png" alt="" />
        <h2>{race?.name}</h2>
        <img id="jobbos" onClick={Increase} src="img/arrow.png" alt="" />
      </div>
      <span>Erő</span>
      <StatBar value={(baseAttributes?.strength || 0)+(race?.bonuses.strength || 0)}></StatBar>
      <span>Ügyesség</span>
      <StatBar value={(baseAttributes?.dexterity || 0)+(race?.bonuses.dexterity || 0)}></StatBar>
      <span>Intelligencia</span>
      <StatBar value={(baseAttributes?.intelligence || 0)+(race?.bonuses.intelligence || 0)}></StatBar>
      <span>Állóképesség</span>
      <StatBar value={(baseAttributes?.constitution || 0)+(race?.bonuses.constitution || 0)}></StatBar>
      <span>Karizma</span>
      <StatBar value={(baseAttributes?.charisma || 0)+(race?.bonuses.charisma || 0)}></StatBar>
      <span>Bölcsesség</span>
      <StatBar value={(baseAttributes?.wisdom || 0)+(race?.bonuses.wisdom || 0)}></StatBar>
    </div>
  );
};

export default App;

问题原因

React的setState是异步更新状态的。在Increase和Decrease函数中,调用setIndex后立刻使用当前的index值去设置race,此时index还未完成更新,所以race拿到的是旧索引对应的种族,导致UI滞后一步。

解决方案

方案1:通过index推导当前种族(推荐)

删除单独维护的race状态,直接通过index从races数组中推导当前种族,避免状态不同步:

const App = () => {
  const [baseAttributes, setBaseAttributes] = useState<BaseAttributesType | null>(null);
  const [races, setRaces] = useState<RacesType[] | null>(null);
  const [index, setIndex] = useState<number>(0);

  // 直接通过索引推导当前种族
  const currentRace = races ? races[index] : null;

  useEffect(() => {
    fetch("data.json")
      .then(res => res.json())
      .then(data => {
        setBaseAttributes(data.base_attributes);
        setRaces(data.races);
      });
  }, []);

  const Increase = () => {
    if (!races) return;
    // 使用函数式更新确保拿到最新的索引值
    setIndex(prevIndex => prevIndex === races.length - 1 ? 0 : prevIndex + 1);
  };

  const Decrease = () => {
    if (!races) return;
    setIndex(prevIndex => prevIndex === 0 ? races.length - 1 : prevIndex - 1);
  };

  return (
    <div className="container">
      <CharacterPreview img={currentRace?.img || ""}></CharacterPreview>
      <div className="sor">
        <img onClick={Decrease} id="balos" src="img/arrow.png" alt="" />
        <h2>{currentRace?.name}</h2>
        <img id="jobbos" onClick={Increase} src="img/arrow.png" alt="" />
      </div>
      <span>Erő</span>
      <StatBar value={(baseAttributes?.strength || 0)+(currentRace?.bonuses.strength || 0)}></StatBar>
      <span>Ügyesség</span>
      <StatBar value={(baseAttributes?.dexterity || 0)+(currentRace?.bonuses.dexterity || 0)}></StatBar>
      <span>Intelligencia</span>
      <StatBar value={(baseAttributes?.intelligence || 0)+(currentRace?.bonuses.intelligence || 0)}></StatBar>
      <span>Állóképesség</span>
      <StatBar value={(baseAttributes?.constitution || 0)+(currentRace?.bonuses.constitution || 0)}></StatBar>
      <span>Karizma</span>
      <StatBar value={(baseAttributes?.charisma || 0)+(currentRace?.bonuses.charisma || 0)}></StatBar>
      <span>Bölcsesség</span>
      <StatBar value={(baseAttributes?.wisdom || 0)+(currentRace?.bonuses.wisdom || 0)}></StatBar>
    </div>
  );
};

方案2:预先计算新索引同步更新

在Increase和Decrease中先算出新索引值,再同步更新index和race:

const Increase = () => {
  if (!races) return;
  const newIndex = index === races.length - 1 ? 0 : index + 1;
  setIndex(newIndex);
  setRace(races[newIndex]);
};

const Decrease = () => {
  if (!races) return;
  const newIndex = index === 0 ? races.length - 1 : index - 1;
  setIndex(newIndex);
  setRace(races[newIndex]);
};

预期效果

点击“下一个”会立即切换到下一个种族,最后一个种族点击后切换到第一个;点击“上一个”反向循环,UI实时更新无滞后。

内容的提问来源于stack exchange,提问作者Bundáskenyér

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 04:24:53