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

关于React useRef在整数与字符串场景下更新行为差异及useEffect依赖作用的技术咨询

搞懂useRef的“慢一步”和useEffect依赖的作用

嘿,我来帮你拆解这里面的核心逻辑,其实都是React的渲染周期和useRef、useState的本质差异在起作用~

先看#1和#2为什么“效果一致”,但本质完全不同

你觉得两个组件输出一样,但它们的运行逻辑天差地别:

  • #1 useRef版:
    rerenderCount是一个存组件外部的“可变容器”,它的变化不会触发组件重渲染。那为什么每次能显示+1后的值?
    因为你的useEffect没有加依赖项,所以每次组件渲染完成后都会执行。比如:

    1. 第一次渲染:组件读取rerenderCount.current = 0,渲染出0;渲染完成后,useEffect执行,把它改成1。
    2. 如果组件因为父组件重渲染等原因再次触发渲染:此时rerenderCount.current已经是1,组件渲染出1;之后useEffect又把它改成2。
      相当于每次渲染显示的是上一次渲染后更新的ref值。
  • #2 useState版:
    rerenderCount是组件的状态,setRerenderCount会触发组件重新渲染。流程是:

    1. 第一次渲染:显示0;渲染完成后useEffect执行,调用setRerenderCount把值改成1,触发第二次渲染。
    2. 第二次渲染:显示1;渲染完成后useEffect又调用setRerenderCount改成2,触发第三次渲染。
      这里每次显示的是本次渲染的最新state值,是靠state更新触发重渲染实现的。

你看到的“效果一致”只是巧合——如果#1的组件不被外部触发重渲染,它永远只会显示0;但#2会自己不断触发重渲染,数值一直涨。

为什么#3里的previousName总是“慢一步”?

这是最关键的点,要抓住React渲染的“快照特性”:
当你在输入框输入内容,调用setName(e.target.value)时,会触发组件重新渲染。在本次渲染过程中:

  • name已经是更新后的新值(比如你输入了"A",name就变成"A")
  • 但previousName.current还是上一次渲染时存在的旧值(比如空字符串)

因为useEffect是在本次渲染完全结束后才会执行的!也就是说,组件先把当前的name和previousName.current(旧值)渲染到页面上,然后才会跑useEffect里的代码,把previousName.current改成新的name。

等你下次输入新内容时,组件再次渲染,这时候previousName.current才是上一次的name,所以看起来总是“慢一步”——它保存的永远是上一次渲染时的name值。

useEffect的[name]依赖项到底有什么用?

你觉得加不加结果一样,是因为你的场景里只有name变化才会触发组件重渲染:

  • 如果不加依赖项:useEffect会在每次组件渲染完成后都执行——不管是name变化导致的重渲染,还是其他原因(比如父组件重渲染、其他state变化)导致的重渲染。
  • 如果加了[name]依赖项:useEffect只会在name的值发生变化时才执行,其他情况的重渲染不会触发它。

举个例子:如果这个组件还有另一个stateage,当你更新age时,组件会重渲染。这时候:

  • 没加依赖的useEffect会再次执行,把previousName.current又设成当前的name(其实name根本没变),做了无用功。
  • 加了[name]的useEffect不会执行,因为name没变化,效率更高,也避免了不必要的操作。

所以依赖项的作用是精准控制useEffect的执行时机,让它只在依赖的状态/ props变化时运行,这也是React官方推荐的写法,能避免很多奇怪的bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 23:03:15