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
相关产品推荐
相关产品推荐

