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

Next.js React应用中handleGrid函数为何触发两次?功能不符预期

问题描述

我要实现这样的单选按钮逻辑:当gridType状态为undefined时,点击按钮就把它设为3/5/7;再次点击同一个按钮,就把状态重置回undefined。handleGrid函数负责处理这个逻辑和状态更新,但它没按预期工作。改成日志函数后发现,这个函数会触发两次:当type和gridType不同时,日志'NO'会输出两次。我已经在next.config.js里设置了{ reactStrictMode: false },想问为什么函数触发两次?原函数为什么失效?

原代码

"use client";

import React, { useContext } from "react";
import { GameContext } from "@/context/GameContext";

const GridSelection = ({ className }: { className?: string }) => {
  const gameContext = useContext(GameContext);
  const { setGridType, gridType }: any = gameContext;

  const handleGrid = (type: 3 | 5 | 7): void => {
    if (type === gridType) {
      setGridType(undefined);
    } else {
      setGridType(type);
    }
  };

  return (
    <fieldset className="text-4xl border-solid border-yellow-500 border-t-2 w-[80%] container mx-auto flex justify-between items-center text-center border-b-2">
      <legend className="pr-4 mb-3">Select Grid</legend>

      <div
        className="mb-4 w-full"
        onClick={() => {
          handleGrid(3);
        }}
      >
        <input
          type="radio"
          name="grid"
          id="3"
          value={gridType}
          className="hidden peer"
        />
        <label
          htmlFor="3"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          3
        </label>
      </div>
      <div className="mb-4 w-full" onClick={() => handleGrid(5)}>
        <input
          type="radio"
          name="grid"
          id="5"
          value={gridType}
          className="hidden peer"
        />
        <label
          htmlFor="5"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          5
        </label>
      </div>
      <div className="mb-4 w-full" onClick={() => handleGrid(7)}>
        <input
          type="radio"
          name="grid"
          id="7"
          value={gridType}
          className="hidden peer"
        />
        <label
          htmlFor="7"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          7
        </label>
      </div>
    </fieldset>
  );
};

export default GridSelection;

日志版handleGrid代码

const handleGrid = (type: 3 | 5 | 7): void => {
    console.log(type);
    console.log(gridType);
    if (type === gridType) {
      console.log(type, "--", gridType);
      // setGridType(undefined);
    } else {
      console.log("No");
      setGridType(type);
    }
  };

日志输出

GridSelection.tsx:11 3
GridSelection.tsx:12 undefined
GridSelection.tsx:17 No

GridSelection.tsx:11 3
GridSelection.tsx:12 3
GridSelection.tsx:14 3 '--' 3

GridSelection.tsx:11 5
GridSelection.tsx:12 3
GridSelection.tsx:17 No

GridSelection.tsx:11 5
GridSelection.tsx:12 5
GridSelection.tsx:14 5 '--' 5

GridSelection.tsx:11 7
GridSelection.tsx:12 5
GridSelection.tsx:17 No

GridSelection.tsx:11 7
GridSelection.tsx:12 7
GridSelection.tsx:14 7 '--' 7

一次点击本该输出11、12、14行或11、12、17行日志,但实际触发两次,输出两组内容。


原因分析与解决方案

1. 函数触发两次的原因

你给<div>绑定了onClick,同时<label>点击会自动触发对应<input>的点击事件,事件冒泡会让<div>的onClick也执行一次,等于一次实际点击触发了两次handleGrid调用。另外,radio输入框本身有默认选中逻辑,点击未选中的radio时,浏览器会自动设置其选中状态,这个行为会和自定义状态更新逻辑冲突,导致组件额外重渲染,进一步增加函数执行次数。

2. 原函数失效的原因

  • 事件重复触发:第一次点击把gridType设为对应数值,第二次触发时因为type和刚更新的gridType相等,又把它设回undefined,两次操作抵消,看起来像没生效。
  • radio属性错误:你把value绑定到gridType,但radio的选中状态应该由checked属性控制,错误的属性绑定导致选中状态和自定义状态完全不同步。

修复步骤

修复后代码示例

"use client";

import React, { useContext } from "react";
import { GameContext } from "@/context/GameContext";

const GridSelection = ({ className }: { className?: string }) => {
  const { setGridType, gridType }: any = useContext(GameContext);

  const handleGridChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const selectedType = parseInt(e.target.id) as 3 | 5 | 7;
    // 点击已选中的radio时,阻止默认选中行为并重置状态
    if (selectedType === gridType) {
      e.preventDefault();
      setGridType(undefined);
    } else {
      setGridType(selectedType);
    }
  };

  return (
    <fieldset className="text-4xl border-solid border-yellow-500 border-t-2 w-[80%] container mx-auto flex justify-between items-center text-center border-b-2">
      <legend className="pr-4 mb-3">Select Grid</legend>

      <div className="mb-4 w-full">
        <input
          type="radio"
          name="grid"
          id="3"
          checked={gridType === 3}
          onChange={handleGridChange}
          className="hidden peer"
        />
        <label
          htmlFor="3"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          3
        </label>
      </div>
      <div className="mb-4 w-full">
        <input
          type="radio"
          name="grid"
          id="5"
          checked={gridType === 5}
          onChange={handleGridChange}
          className="hidden peer"
        />
        <label
          htmlFor="5"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          5
        </label>
      </div>
      <div className="mb-4 w-full">
        <input
          type="radio"
          name="grid"
          id="7"
          checked={gridType === 7}
          onChange={handleGridChange}
          className="hidden peer"
        />
        <label
          htmlFor="7"
          className="p-1 peer-checked:border-yellow-500 peer-checked:border-solid peer-checked:border-2 peer-checked:rounded-md peer-checked:text-yellow-500 border-gray-700 border-solid border-2 rounded-md transition-colors text-gray-700"
        >
          7
        </label>
      </div>
    </fieldset>
  );
};

export default GridSelection;

修复说明

  • 移除<div>上的onClick,改用radio的onChange事件,避免事件冒泡导致的重复触发。
  • 给每个radio设置checked={gridType === 对应数值},让选中状态和自定义状态同步。
  • 在handleGridChange中,点击已选中的radio时,通过e.preventDefault()阻止浏览器默认选中行为,再重置gridType为undefined,实现二次点击取消选中的逻辑。

这样修改后,点击事件只会触发一次,状态更新逻辑也能正常工作。


内容的提问来源于stack exchange,提问作者Mehul Kumar Srivastava

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:38:08