NextJS组件输入变更时重复渲染及失焦问题求助
问题解决:输入框失焦与组件重复渲染
核心问题分析
你遇到的输入框失焦、组件重复渲染问题,根源有两个:
- 内部组件重复创建:
Calculator组件定义在父组件的函数体内,每次父组件因状态更新(比如输入框触发setIotInitial)重新渲染时,都会重新创建一个全新的Calculator组件。这会导致React卸载旧的组件DOM,重新挂载新的,输入框自然就失焦了。 - 受控组件属性错误:你的input同时使用了
defaultValue和value,且value设置成了固定字符串(比如value="initial"),这违背了受控组件的逻辑——受控组件的value应该直接绑定状态变量,否则输入内容无法和状态同步,还会引发额外渲染。
修复方案
1. 把Calculator组件移到父组件外部
将组件定义移出父组件函数范围,避免每次父渲染都重建组件:
// 把Calculator移到父组件外面 const Calculator = ({ calc, iotInitial, iotCont, iotGrowth, iotSubmit, setIotInitial, setIotCont, setIotGrowth, setIotSubmit }) => { if (calc === "1") { return ( <div className="text-black p-2"> <h1 className="text-lg">Investment Over Time</h1> <div> <div className="flex flex-wrap gap-x-5"> <div className="flex flex-col"> <label>Initial Investment</label> {/* 修正value绑定,移除defaultValue */} <input value={iotInitial} type="number" className="rounded" onChange={(e) => setIotInitial(Number(e.target.value))} /> </div> <div className="flex flex-col"> <label>Contributions (monthly)</label> <input value={iotCont} type="number" className="rounded" onChange={(e) => setIotCont(Number(e.target.value))} /> </div> <div className="flex flex-col"> <label>Growth Time (years)</label> <input value={iotGrowth} type="number" className="rounded" onChange={(e) => setIotGrowth(Number(e.target.value))} /> </div> <button className="bg-blue-300 hover:bg-blue-500 px-5 rounded" onClick={() => setIotSubmit(true)}> Submit </button> </div> {iotSubmit && <div> {iotInitial} {iotCont} {iotGrowth} </div> } </div> </div> ); } else if (calc === "2") { return ( <div className="text-black p-2"> <h1 className="text-lg">Risk Analysis Using Average True Range</h1> <p>Coming Soon</p> </div> ); } else if (calc === "3") { return ( <div className="text-black"> <h1 className="text-lg">Hello</h1> <p>{calc}</p> </div> ); } }; // 父组件 const ParentComponent = () => { const [calc, setCalc] = useState(""); const [iotInitial, setIotInitial] = useState(0); const [iotCont, setIotCont] = useState(0); const [iotGrowth, setIotGrowth] = useState(0); const [iotSubmit, setIotSubmit] = useState(false) return <Calculator calc={calc} iotInitial={iotInitial} iotCont={iotCont} iotGrowth={iotGrowth} iotSubmit={iotSubmit} setIotInitial={setIotInitial} setIotCont={setIotCont} setIotGrowth={setIotGrowth} setIotSubmit={setIotSubmit} />; };
2. 修正受控组件的状态绑定
- 移除
defaultValue,因为受控组件只需要value绑定状态; - 将输入的字符串值转为数字(用
Number()),因为你的初始状态是数字类型,保持类型一致避免潜在问题; - 确保
value直接对应状态变量,比如value={iotInitial},这样输入内容会和状态同步更新。
额外说明
如果因为某些原因无法把Calculator移到外部,也可以用useCallback包裹组件定义,配合React.memo来优化渲染:
const ParentComponent = () => { // ... 状态定义 const Calculator = useCallback(() => { // 组件内容不变,但要确保内部用到的状态和方法都是稳定引用 }, [calc, iotInitial, iotCont, iotGrowth, iotSubmit, setIotInitial, setIotCont, setIotGrowth, setIotSubmit]); return <React.memo(Calculator) />; };
不过第一种移到外部的方案更简洁,也更容易维护。
内容的提问来源于stack exchange,提问作者Dotman
相关产品推荐
相关产品推荐

