关于React useRef在整数与字符串场景下更新行为差异及useEffect依赖作用的技术咨询
嘿,我来帮你拆解这里面的核心逻辑,其实都是React的渲染周期和useRef、useState的本质差异在起作用~
先看#1和#2为什么“效果一致”,但本质完全不同
你觉得两个组件输出一样,但它们的运行逻辑天差地别:
#1 useRef版:
rerenderCount是一个存组件外部的“可变容器”,它的变化不会触发组件重渲染。那为什么每次能显示+1后的值?
因为你的useEffect没有加依赖项,所以每次组件渲染完成后都会执行。比如:- 第一次渲染:组件读取
rerenderCount.current = 0,渲染出0;渲染完成后,useEffect执行,把它改成1。 - 如果组件因为父组件重渲染等原因再次触发渲染:此时
rerenderCount.current已经是1,组件渲染出1;之后useEffect又把它改成2。
相当于每次渲染显示的是上一次渲染后更新的ref值。
- 第一次渲染:组件读取
#2 useState版:
rerenderCount是组件的状态,setRerenderCount会触发组件重新渲染。流程是:- 第一次渲染:显示
0;渲染完成后useEffect执行,调用setRerenderCount把值改成1,触发第二次渲染。 - 第二次渲染:显示
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

