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

Next.js登录组件表单提交后累计重复渲染问题求助

登录组件重复渲染问题的原因及修复方案

核心原因分析

  • 重复绑定表单提交事件:组件每次渲染时都会执行document.getElementById("form_")?.addEventListener("submit", ...),每渲染一次就新增一个submit事件监听器。后续提交表单时,所有已绑定的监听器都会触发,每次触发又可能引发状态更新,导致重渲染次数随提交次数累计。
  • 直接操作DOM违背React数据流:代码中通过innerHTML直接修改span内容,跳过了React的状态管理机制,这种操作会和React的虚拟DOM更新冲突,进一步加剧渲染异常。
  • 状态更新依赖旧状态未用函数式更新:调用setIsErrExist({ ...isErrExist, status: true })时,依赖闭包中的旧状态值,多次触发状态更新时可能导致状态更新不及时或不准确,引发额外重渲染。

修复后的代码

"use client";

import React, { useState } from "react";
import { z } from "zod";
import styles from "./index.module.scss";
import Link from "next/link";
import { useGetUserMutation } from "@/src/app/store/features/api/apiSlice";

function Login() {
  console.log("render");
  const [formData, setFormData] = useState({ email: "", password: "" });
  const [errors, setErrors] = useState({ email: "", password: "" });
  const [getUser, { isLoading }] = useGetUserMutation();

  const userSchema = z.object({
    email: z.string().email("请输入有效的邮箱地址"),
    password: z
      .string()
      .min(6, "密码长度不能少于6位")
      .max(10, "密码长度不能超过10位"),
  });

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    // 清空之前的错误
    setErrors({ email: "", password: "" });

    const result = userSchema.safeParse(formData);
    if (!result.success) {
      const errorObj = result.error.issues.reduce((acc, issue) => {
        acc[issue.path[0] as keyof typeof errors] = issue.message;
        return acc;
      }, {} as typeof errors);
      setErrors(errorObj);
      return;
    }

    getUser(formData)
      .unwrap()
      .then((res) => console.log(res))
      .catch((err) => console.log(err));
  };

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    // 输入时清空对应字段的错误
    if (errors[name as keyof typeof errors]) {
      setErrors(prev => ({ ...prev, [name]: "" }));
    }
  };

  return (
    <form onSubmit={handleSubmit} className={styles.form_}>
      <h2>Login</h2>
      <label htmlFor="email_">E-mail Adress:</label>
      <input
        type="email"
        name="email"
        id="email_"
        value={formData.email}
        onChange={handleInputChange}
      />
      {errors.email && <span className={styles.err}>{errors.email}</span>}
      
      <label htmlFor="password_">Password:</label>
      <input
        type="password"
        name="password"
        id="password_"
        value={formData.password}
        onChange={handleInputChange}
      />
      {errors.password && <span className={styles.err}>{errors.password}</span>}
      
      <button type="submit" disabled={isLoading}>
        {isLoading ? "Loading..." : "Send"}
      </button>
      <Link href="register">
        <h4>Go To Register Page</h4>
      </Link>
    </form>
  );
}

export default Login;

关键修复说明

  1. 使用React原生表单绑定:通过onSubmit直接在form元素上绑定提交事件,避免手动操作DOM添加监听器导致的重复绑定问题。
  2. 状态管理表单与错误信息:用formData存储输入值,errors存储错误提示,完全遵循React数据流,不再直接操作DOM。
  3. 函数式更新状态:更新表单数据时使用setFormData(prev => ({ ...prev, [name]: value })),确保每次状态更新基于最新值。
  4. 优化错误处理:通过Zod校验结果生成精准的错误对象,输入时自动清空对应字段的错误提示,提升用户体验。

内容的提问来源于stack exchange,提问作者Hakan Karayılmaz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:12:49