React中onBlur、onChange、onClick事件触发两次的问题解决
React useReducer 表单事件触发两次问题排查与解决
我使用React的useReducer构建表单时,发现onBlur、onChange和onClick事件均会触发两次,在reducer函数中添加console.log可以复现该问题,相关JSX代码如下:
import React, { useReducer } from "react"; const LongForm = () => { const initialState = { firstName: "", lastName: "", email: "", gender: "", education: "", quantity: 0, feedback: "", term: false, }; const reducer = (state, action) => { switch (action.type) { case "INPUT": { return { ...state, [action.payload.name]: action.payload.value, }; } case "TOGGLE": { return { ...state, term: !state.term, }; } default: return state; } }; const [state, dispatch] = useReducer(reducer, initialState); const submit = (event) => { event.preventDefault(); }; return ( <div> <form onSubmit={submit} > <div> <label htmlFor="firstName"> First Name </label> <input type="text" name="firstName" id="firstName" onBlur={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> </div> <div> <label htmlFor="lastName"> Last Name </label> <input type="text" name="lastName" id="lastName" onBlur={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> </div> <div> <label htmlFor="email"> Email </label> <input type="email" name="email" id="email" onBlur={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> </div> <div> <h1>Gender</h1> <div> <div> <input type="radio" id="male" name="gender" value="male" onClick={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> <label htmlFor="male"> Male </label> </div> <div> <input type="radio" id="female" name="gender" value="female" onClick={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> <label htmlFor="female"> Female </label> </div> <div> <input type="radio" id="other" name="gender" value="other" onClick={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} /> <label htmlFor="other"> Other </label> </div> </div> </div> <div> <label htmlFor="education"> Education </label> <select name="education" id="education" onChange={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} > <option value="SSC">SSC</option> <option value="HSC">HSC</option> <option value="underGrad">Under Graduate</option> <option value="graduate">Graduate</option> </select> </div> <div> <label>Number of PCs</label> <div> <button> - </button> <div> <span>0</span> </div> <button> + </button> </div> </div> <div> <label htmlFor="feedback"> Feedback </label> <textarea name="feedback" id="feedback" cols="30" rows="4" onBlur={(e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); }} ></textarea> </div> <div> <div> <input type="checkbox" name="term" id="terms" onClick={() => dispatch({ type: "TOGGLE" })} /> <label htmlFor="terms">I agree to terms and conditions</label> </div> <button type="submit" > Submit </button> </div> </form> </div> ); }; export default LongForm;
问题原因与解决方法
1. 核心原因:React严格模式(Strict Mode)
这是最常见的诱因。React的<React.StrictMode>在开发环境下会重复调用reducer、事件处理函数等,目的是检测潜在的副作用问题,比如意外的状态突变、重复的订阅/取消订阅等。生产环境下这种重复调用会自动禁用,不会影响用户体验。
解决方式:
检查项目入口文件(通常是index.js),如果根组件被<React.StrictMode>包裹,暂时移除它即可在开发环境中消除事件两次触发的现象。示例:
// 移除前 ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <App /> </React.StrictMode> ); // 移除后 ReactDOM.createRoot(document.getElementById('root')).render( <App /> );
注意:严格模式是React提供的开发辅助工具,建议开发完成后再恢复,帮助排查潜在问题。
2. 其他可能的排查方向
- 重复事件绑定:检查是否给同一个元素绑定了两次相同的事件处理函数。从你的代码来看,每个事件都是直接内联定义的,不存在重复绑定问题。
- 组件重复渲染:如果父组件的状态变化导致当前表单组件重复渲染,也可能让事件看起来触发两次,但结合你的代码,这种概率极低,优先排查严格模式。
代码优化建议
为了减少代码冗余,建议抽离通用的事件处理函数:
const handleInputUpdate = (e) => { dispatch({ type: "INPUT", payload: { name: e.target.name, value: e.target.value, }, }); };
之后在表单元素中直接复用:
// 输入框 <input type="text" name="firstName" id="firstName" onBlur={handleInputUpdate} /> // 单选按钮推荐用onChange替代onClick(点击label也能触发) <input type="radio" id="male" name="gender" value="male" onChange={handleInputUpdate} />
内容的提问来源于stack exchange,提问作者Thakur Saad
相关产品推荐
相关产品推荐

