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
相关产品推荐
相关产品推荐

