在React的世界里,有一个广泛流传的误解:每当组件的props发生变化,React就会自动触发重渲染。这种说法是不正确的,实际上,React的更新逻辑复杂,它涉及到一系列精妙的优化机制,这些机制决定了何时以及为何进行重渲染,但我们日常使用触发重渲染的事件只有一个,那就是 通过 setState 来更改 state 的。
接下来,就从 useState 和 useRef 中来看 React 更新机制。
在React中,useState 和 useRef 都是React Hook,用于在函数组件中管理状态和引用。区别是 state 的变化会引起 React 的更新,ref 的变化不会。
下面就使用代码来解释这个情况
react 中页面更新实际上就是重新执行了一遍这个组件函数
在代码加了一行打印,你在自己实践的时候也可以观察一下。
import React, { useState, useRef } from "react";
const Children = ({ name, value }: { name: string; value: number }) => {
// 每一次子组件更新时,都会触发
console.log(name, ":", value, "render");
return (
<div>
{name}-Children:{value}
</div>
);
};
const CounterWithRef = () => {
// 使用 useState 来管理计数状态
const [count, setCount] = useState(0);
// 使用 useRef 来存储计数的持久性引用,不会导致重新渲染
const countRef = useRef(0);
// 每一次页面更新时,都会触发
console.log("count:", count, "countRef.current:", countRef.current, "render");
const handleAdd = () => {
// 使用 useState 更新状态,并触发重新渲染
setCount((prevCount) => prevCount + 1);
};
const handleRefAdd = () => {
// 使用 useRef 直接更新引用,不触发重新渲染
countRef.current = countRef.current + 1;
};
const handleCheck = () => {
// 用于实时查看 useState 和 useRef 的值
console.log(
"count:",
count,
"countRef.current:",
countRef.current,
"check"
);
};
return (
<div>
<div style={{ display: "flex", gap: "6px" }}>
<p>Count (useState): {count}</p>
<button type="button" onClick={handleAdd}>
add
</button>
</div>
<div style={{ display: "flex", gap: "6px" }}>
<p>Count (useRef): {countRef.current}</p>
<button onClick={handleRefAdd}>refAdd</button>
</div>
<div onClick={handleCheck}>check</div>
<Children name="useState" value={count}></Children>
<Children name="useRef" value={countRef.current}></Children>
</div>
);
};
export default CounterWithRef;
在代码中,我加入了check的按钮,你可以通过点击来实时查看 state 和 ref 的值。
- 每一次点击 add 都会触发页面更新,打印也会打印,而且 useState 的 Children 同步更新。
- 每一次点击 refAdd 页面不会更新,打印也没有打印,而且 useRef 的 Children 不会更新,点击check 可以看到 countRef.current 的值确实变化了。这也就证明了 props 的更新不会引起 react 的页面更新。
- 在点击 refAdd 后,再点击 add ,现象和 1 一样,而且打印的值都是最新的,2个子组件都更新。也就是说 state 的变化引起了整个组件的更新,所以误解就是在这里来的,实际上是因为你把父组件的 state 当子组件的 props 传递下去,而 state 的变化引起了整个组件的更新,子组件也跟着更新。
最后再留个问题,每一次点击 add ,2个子组件的打印都会打印,但是 useRef 的子组件的 props 并没有更新, 理论上来说是不需要更新的。那么应该如何优化呢,使得点击 add 后只有 countRef.current 的值变了,countRef.current 的子组件才更新。