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

React中如何动态设置值?动态输入块个性化计算结果赋值问题

问题描述

我开发了一个可动态渲染输入框的应用,需要将计算结果分别设置到每个区块中类名为strongCrist和strongRuby的span标签内,但目前所有区块的span都被设置为相同的值。在纯JavaScript中实现很简单——通过循环给span添加innerHTML,再用parentElement.appendChild添加到父元素中。

原代码
const { useState, useEffect } = React;

function PrimaryInputs() {
  const [playersQuantity, setPlayersQuantity] = useState(0);
  const [cristQuantity, setCristQuantity] = useState(0);
  const [rubyQuantity, setRubyQuantity] = useState(0);
  const [isCalculated, setIsCalculated] = useState(false);
  const [inputs, setInputs] = useState({});
  const [cupsQuantity, setCupsQuantity] = useState(0);
  const [dole, setDole] = useState(0);
  const [doleCrist, setDoleCrist] = useState(0);
  const [doleRuby, setDoleRuby] = useState(0);

  function playersHandleChange(event) {
    setPlayersQuantity(event.target.value);
  }
  function cristHandleChange(event) {
    setCristQuantity(event.target.value);
  }
  function rubyHandleChange(event) {
    setRubyQuantity(event.target.value);
  }
  function iterator(quantity) {
    let elems = [];
    for (var i = 0; i < quantity; i++) {
      elems.push(
        <div
          className={`playersBlock ${isCalculated ? "calculated" : ""} `}
          key={`player${i}`}
        >
          <input
            type="number"
            name={`cupsOf${i}`}
            placeholder="players cups"
            className="plCup"
            onChange={cupsHandleChange}
          />
          <br />
          <span className="strongCrist">Cristalls: {doleCrist}</span>
          <br />
          <span className="strongRuby">Rubies: {doleRuby}</span>
        </div>
      );
    }
    return elems;
  }
  function cupsHandleChange(event) {
    const { name, value } = event.target;
    setInputs((prev) => {
      return { ...prev, [name]: Number(value) };
    });
    setIsCalculated(true);
  }

  function countCups() {
    let i = 0;
    for (const key in inputs) {
      const val = inputs[key];
      if (val) {
        i += val;
      }
      setCupsQuantity(i);
    }
  }

  function countDole() {
    for (const key in inputs) {
      const val = inputs[key];
      if (val) {
        setDole(val / cupsQuantity);
        setDoleCrist(Math.round(dole * cristQuantity));
        setDoleRuby(Math.round(dole * rubyQuantity));
      }
    }
  }

  useEffect(() => {
    countCups();
    countDole();
  });

  return (
    <>
      <div className="mainInputs">
        <div className="input_wrapper">
          <label htmlFor="players">Enter quantity of players</label>
          <input
            type="number"
            name="players"
            id="players"
            onChange={playersHandleChange}
          />
        </div>
        <div className="input_wrapper">
          <label htmlFor="cristall">Enter cristalls</label>
          <input
            type="number"
            name="cristall"
            id="allCrist"
            onChange={cristHandleChange}
          />
        </div>
        <div className="input_wrapper">
          <label htmlFor="ruby">Enter rubies</label>
          <input
            type="number"
            name="ruby"
            id="allRuby"
            onChange={rubyHandleChange}
          />
        </div>
      </div>
      <hr />
      <span>
        {playersQuantity ? `players quantity: ${playersQuantity}` : null}
      </span>
      <br />
      <span className="sumCup">
        {cupsQuantity ? `cups quantity: ${cupsQuantity}` : null}
      </span>
      <div id="playersList">{iterator(playersQuantity)}</div>
    </>
  );
}

ReactDOM.render(
  <>
    <PrimaryInputs />
  </>,
  document.getElementById("mydiv")
);
问题原因

所有玩家区块共享了doleCrist和doleRuby这两个全局状态,导致无论哪个玩家的杯子数变化,所有区块都会显示最后一次计算的相同值。React中状态是组件级别的,全局状态无法区分单个玩家的计算结果。

解决方案

移除全局的分配状态,改为在渲染每个玩家区块时,根据该玩家的杯子数和总杯子数、总资源数实时计算对应的值。同时优化状态更新逻辑,避免不必要的重复渲染。

修改后的代码
const { useState, useEffect } = React;

function PrimaryInputs() {
  const [playersQuantity, setPlayersQuantity] = useState(0);
  const [cristQuantity, setCristQuantity] = useState(0);
  const [rubyQuantity, setRubyQuantity] = useState(0);
  const [isCalculated, setIsCalculated] = useState(false);
  const [inputs, setInputs] = useState({});
  const [cupsQuantity, setCupsQuantity] = useState(0);

  function playersHandleChange(event) {
    setPlayersQuantity(Number(event.target.value));
    // 切换玩家数量时重置输入
    setInputs({});
    setIsCalculated(false);
  }
  
  function cristHandleChange(event) {
    setCristQuantity(Number(event.target.value));
  }
  
  function rubyHandleChange(event) {
    setRubyQuantity(Number(event.target.value));
  }
  
  function iterator(quantity) {
    let elems = [];
    for (let i = 0; i < quantity; i++) {
      const playerCups = inputs[`cupsOf${i}`] || 0;
      // 实时计算当前玩家的分配值
      const playerDole = cupsQuantity > 0 ? playerCups / cupsQuantity : 0;
      const playerCrist = Math.round(playerDole * cristQuantity);
      const playerRuby = Math.round(playerDole * rubyQuantity);
      
      elems.push(
        <div
          className={`playersBlock ${isCalculated ? "calculated" : ""}`}
          key={`player${i}`}
        >
          <input
            type="number"
            name={`cupsOf${i}`}
            placeholder="玩家杯子数"
            className="plCup"
            onChange={cupsHandleChange}
            value={playerCups || ""}
          />
          <br />
          <span className="strongCrist">水晶: {playerCrist}</span>
          <br />
          <span className="strongRuby">红宝石: {playerRuby}</span>
        </div>
      );
    }
    return elems;
  }
  
  function cupsHandleChange(event) {
    const { name, value } = event.target;
    setInputs(prev => ({
      ...prev,
      [name]: Number(value) || 0
    }));
    setIsCalculated(true);
  }

  function countCups() {
    const total = Object.values(inputs).reduce((sum, val) => sum + (val || 0), 0);
    setCupsQuantity(total);
  }

  useEffect(() => {
    countCups();
  }, [inputs]); // 仅在inputs变化时重新计算总杯子数

  return (
    <>
      <div className="mainInputs">
        <div className="input_wrapper">
          <label htmlFor="players">输入玩家数量</label>
          <input
            type="number"
            name="players"
            id="players"
            onChange={playersHandleChange}
            min="0"
          />
        </div>
        <div className="input_wrapper">
          <label htmlFor="cristall">输入水晶总数</label>
          <input
            type="number"
            name="cristall"
            id="allCrist"
            onChange={cristHandleChange}
            min="0"
          />
        </div>
        <div className="input_wrapper">
          <label htmlFor="ruby">输入红宝石总数</label>
          <input
            type="number"
            name="ruby"
            id="allRuby"
            onChange={rubyHandleChange}
            min="0"
          />
        </div>
      </div>
      <hr />
      <span>
        {playersQuantity > 0 ? `玩家数量: ${playersQuantity}` : null}
      </span>
      <br />
      <span className="sumCup">
        {cupsQuantity > 0 ? `总杯子数: ${cupsQuantity}` : null}
      </span>
      <div id="playersList">{iterator(playersQuantity)}</div>
    </>
  );
}

ReactDOM.render(
  <PrimaryInputs />,
  document.getElementById("mydiv")
);
关键改动说明
  • 移除了全局的dole、doleCrist、doleRuby状态,改为在渲染每个玩家区块时实时计算分配值,确保每个玩家显示自己的结果。
  • 优化countCups函数,使用reduce计算总杯子数,避免循环中多次调用setState。
  • 给useEffect添加了依赖项[inputs],只有当玩家杯子数变化时才重新计算总杯子数,减少不必要的重渲染。
  • 给输入框添加了value属性,实现受控组件,确保输入值和状态同步。
  • 切换玩家数量时重置输入状态,避免旧数据残留。

内容的提问来源于stack exchange,提问作者kahjuksEi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 22:20:22