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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:35:24