为什么我的 React 组件会无限循环?—— 一次由 `onClick` 引发的“惨案”分析

为什么我的 React 组件会无限循环?------ 一次由 onClick 引发的"惨案"分析

作者:你的专属AI老师 目标读者:訾博,以及所有在 React 探索之路上勇敢前行的开发者们

大家好!今天我们来当一回"代码侦探",侦破一个在 React 新手村里频繁发生的"神秘案件"。

案发现场

一切都从一段看似人畜无害的代码开始:

javascript 复制代码
import { useState } from "react";

function Counter() {
    const [num, setNum] = useState(0);

    return (
        <>
            <p>{num}</p>
            {/* 问题就出在这一行! */}
            <button onClick={setNum(num => num + 1)}>增加</button>
        </>
    )
}

export default Counter;

这段代码的意图非常明确:点击按钮,数字加一。逻辑清晰,代码简洁。然而,当你运行它时,浏览器可能会崩溃,并且控制台会无情地抛出一条错误信息:"Too many re-renders"(重渲染次数过多)。

这是为什么?我们明明还没点击按钮,程序怎么就陷入了自我毁灭的无限循环之中?

侦破过程:命令书 vs 说明书

问题的根源在于,我们混淆了"立即下达命令"和"递交一份操作说明书"的区别。

在 React 的世界里,组件的每一次渲染,都可以看作是工厂在按照"图纸"(也就是你的 JSX 代码)来组装一个产品。

当我们写 onClick={...} 的时候,我们的本意是告诉按钮:"嘿,这里有一份 '说明书',当有用户点击你的时候,你就按照这份说明书上的步骤去操作。"

然而,我们写的代码 onClick={setNum(num => num + 1)} 实际上在说什么呢?

注意 setNum(...) 后面的这对圆括号 () 。在 JavaScript 中,圆括号意味着 "立即调用(执行)这个函数!"

所以,整个流程变成了这样:

  1. React 开始渲染 Counter 组件。它从上到下读取你的 JSX "图纸"。
  2. 当它读到 <button> 这一行时,它看到了 onClick={setNum(num => num + 1)}
  3. 它发现这是一个函数调用 ,于是它想:"哦,一个立即执行的命令!" 于是它立刻执行了 setNum 函数。
  4. setNum 函数更新了 num 这个 state
  5. React 的核心法则之一:任何 state 的更新,都会触发组件的重新渲染
  6. 好了,组件开始重新渲染,回到了第 1 步。
  7. 在新的渲染过程中,它再次读到 <button>,再次立即执行 setNum,再次更新 state,再次触发重渲染......

看到了吗?一个没有尽头的死亡循环就此诞生。React 为了自保,只能强制停止并报错。

结案陈词:正确的"说明书"递交方式

那么,如何正确地给 onClick 递交一份"说明书"(也就是一个函数定义函数引用),而不是一个"立即执行的命令"呢?

方案一:箭头函数包装法(最常用)

javascript 复制代码
<button onClick={() => setNum(num => num + 1)}>增加</button>

这是最经典的修正方法。我们用 () => ... 创建了一个新的匿名箭头函数

这个箭头函数本身就是一份"说明书"。 我们把这份说明书交给了 onClick。React 在渲染时,看到的是一个函数定义,所以它不会执行。只有当用户真正点击按钮时,onClick 事件被触发,这个箭头函数才会被调用,进而执行它内部的 setNum(...) 命令。

方案二:独立函数引用法(代码更清晰)

当逻辑变得复杂时,更好的方式是定义一个独立的事件处理函数。

javascript 复制代码
function Counter() {
    const [num, setNum] = useState(0);

    // 1. 先把"说明书"写好,并给它取个名字
    const handleClick = () => {
        setNum(num => num + 1);
    };

    return (
        <>
            <p>{num}</p>
            {/* 2. 把"说明书"的名字(函数引用)交给 onClick */}
            <button onClick={handleClick}>增加</button>
        </>
    )
}

在这里,onClick={handleClick} 传递的是 handleClick 这个函数本身(它的引用 ),而不是 handleClick()(它的执行结果)。这同样达到了"递交说明书"的目的,并且让你的 JSX 结构更加干净。

铭记于心:核心思想

React 事件处理器(如 onClick, onChange 等)需要的,是一个 *"待执行的函数",而不是一个"已经执行完毕的函数结果"**。*

你需要时刻警惕 JSX ={} 中的圆括号 ()。它通常意味着"立即执行",这在事件处理的场景下,往往是你最不希望发生的事情。

訾博,请记住,踩过这个"坑",你就获得了一枚宝贵的 React 勋章。这不仅是一个语法错误,它背后反映了 React 声明式UI状态驱动渲染 的核心理念。理解了它,你就离 React 大师更近了一步!


背诵金句

在 JSX 的事件属性(如 onClick)中,{} 里应该传递一个 函数本身 (函数定义或引用),而不是 函数的调用结果 。带圆括号 () 的是调用,会立即在渲染时执行;不带圆括号的(或被箭头函数包裹的)是定义,会等待事件触发后才执行。

相关推荐
訾博ZiBo3 小时前
React状态更新之谜:为何大神偏爱`[...arr]`,而非`arr.push()`?
react.js
my一阁3 小时前
一文解决Chrome使用
前端·chrome
IT_陈寒3 小时前
SpringBoot性能调优实战:5个让接口响应速度提升300%的关键配置
前端·人工智能·后端
訾博ZiBo3 小时前
告别 v-model 焦虑:在 React 中优雅地处理『双向绑定』
前端·react.js
β添砖java4 小时前
交互动效设计
前端·javascript·交互
简小瑞4 小时前
VSCode用它管理上千个服务:依赖注入从入门到实战
前端·设计模式
骑自行车的码农5 小时前
React 合成事件的设计原理 2
前端·react.js
JamesGosling6665 小时前
详解 Vue 3.6 Vapor Mode:从原理到问题,看透 VDOM 逐步退场的底层逻辑
前端·vue.js
一个很帅的帅哥5 小时前
Vue中的hash模式和history模式
前端·vue.js·history模式·hash模式