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

React组件渲染/重渲染困惑:局部变量i的异常行为解析

React局部变量的闭包陷阱:按钮点击数值异常解析

问题现象

点击「Change the alphabets」按钮时,第一次显示pqrts6,连续快速点击两次后显示pqrts7,等组件更新完成后再点击又回到pqrts6。原本误以为React会保留局部变量i的值,应该始终显示pqrts6,实际行为却不符合预期。

组件代码

import { useState, useReducer } from 'react'

const countReducer = (state, newState) => ({ ...state, ...newState })

function Counter({ initialCount = 0, step = 1 }) {
  const [me, setMe] = useState('ABc');
  const [state, setState] = useReducer(countReducer, { count: initialCount })
  const count = state.count

  let i = 5;
  console.log('i->' + i)
  
  const changeVal = () => {
    console.log('curre val ' + me)
    console.log('i val inside**' + i)
    i = i + 1
    setMe('pqrts' + i)
  }
  
  console.log('i->after ' + i)
  console.log('next val ' + me)

  const increment = () => setState({ count: count + step })
  
  return (
    <>
      <button onClick={changeVal}>Change the alphabets: {me}</button>
      <button onClick={increment}>{count}</button>
    </>
  )
}

问题根源

  1. 组件渲染时局部变量会重置:React组件每次状态更新(比如调用setMe)都会重新执行整个组件函数,每次执行时let i=5都会重新声明并赋值为5,局部变量不会在不同渲染周期之间保留值。
  2. 闭包的捕获特性:changeVal是组件每次渲染时创建的闭包函数,它会捕获当前渲染周期里i的引用:
    • 首次点击:调用初始渲染生成的changeVal,i从5变为6,调用setMe触发组件重新渲染。如果此时快速连续点击第二次,组件还没完成重新渲染,仍然调用同一个changeVal,此时i已经是6,再加1变成7,setMe后组件渲染显示pqrts7。
    • 组件完成重新渲染后,新的changeVal会捕获新的i=5,此时点击就会把i变成6,显示pqrts6,如此循环。

解决方案

如果需要在渲染之间保留变量值,不能用普通局部变量,推荐用useState或useRef:

方案1:使用useState

通过状态管理变量,确保每次更新都基于最新值:

import { useState, useReducer } from 'react'

const countReducer = (state, newState) => ({ ...state, ...newState })

function Counter({ initialCount = 0, step = 1 }) {
  const [me, setMe] = useState('ABc');
  const [state, setState] = useReducer(countReducer, { count: initialCount })
  const count = state.count
  // 用useState保存i的值
  const [i, setI] = useState(5);
  
  const changeVal = () => {
    setI(prevI => {
      const newI = prevI + 1;
      setMe('pqrts' + newI);
      return newI;
    });
  }

  const increment = () => setState({ count: count + step })
  
  return (
    <>
      <button onClick={changeVal}>Change the alphabets: {me}</button>
      <button onClick={increment}>{count}</button>
    </>
  )
}

方案2:使用useRef

useRef的current属性可以在渲染之间保留值,且修改它不会触发组件重新渲染:

import { useState, useReducer, useRef } from 'react'

const countReducer = (state, newState) => ({ ...state, ...newState })

function Counter({ initialCount = 0, step = 1 }) {
  const [me, setMe] = useState('ABc');
  const [state, setState] = useReducer(countReducer, { count: initialCount })
  const count = state.count
  // 用useRef保存i的值
  const iRef = useRef(5);
  
  const changeVal = () => {
    iRef.current += 1;
    setMe('pqrts' + iRef.current);
  }

  const increment = () => setState({ count: count + step })
  
  return (
    <>
      <button onClick={changeVal}>Change the alphabets: {me}</button>
      <button onClick={increment}>{count}</button>
    </>
  )
}

内容的提问来源于stack exchange,提问作者Rameshwar Mandavgade

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:57:48