很多人写 React 会随手就写
useState,但你真的分清:这个变量,是否需要驱动 UI 更新?
业务需求
点击按钮,累计点击次数,点击≥5 次之后,展示组件期间的 render 次数。
注意:页面并不需要展示点击次数本身,只需要在达到阈值后触发展示逻辑。
直觉写法:直接用 useState
很多人第一反应,点击次数是变化的数据,直接上 useState。
const [count, setCount] = useState(0);
const [showRender, setShowRender] = useState(false);
function handleClick() {
setCount(count + 1);
if(count >=5) {
setShowRender(true);
}
}
return (
<div>
{/*
<div>点击了 {count} 次</div> */}
<button onClick={handleClick}>点击</button>
{ showRender &&
<div>展示render次数</div> }
</div>
)
一开始我们会把 count 渲染到页面上:点击了 {count} 次,此时 count 要驱动界面显示,用 useState 完全合理。
但需求变更:不需要页面显示点击次数,只拿它做内部判断条件。于是我们把渲染 count 的 div 注释 / 删掉。
问题来了:count 依旧是 state,每次 setCount 都会触发组件重新渲染。 即便页面完全不需要显示 count,每点一次按钮,组件都会执行一次 render,直到满足大于等于 5 次条件。
核心思考:这个变量和 UI 还有关系吗?
当我们删掉展示 count 的 DOM 之后:状态变量和 UI 之间的关联被切断了。
useState 的本质:变量变化,触发组件重渲染,更新界面。
如果一个数据变化,不需要改变页面上任何 DOM,仅仅用于业务逻辑判断、做计数、存临时实例,那就不该放在 state 里。
这种只需要 “记住一个值,不驱动界面更新” 的数据,应该交给 useRef。
使用 useRef 重构
const clickCountRef = useRef(0);
const [showRender, setShowRender] = useState(false);
const handleClick = () => {
clickCountRef.current += 1;
// ref修改不会触发重渲染
if (clickCountRef.current >= 5) {
setShowRender(true);
}
};
return (
<div>
<button onClick={handleClick}>点击</button>
{showRender &&
<div>展示render次数</div>}
</div>
);
效果完全一致,但是性能发生变化:
点击 1‑4 次的时候,修改 ref.current 不会触发组件重渲染;
直到第 5 次,执行 setShowRender,才会触发唯一一次渲染。
useState 和 useRef 的简单区分口诀
- ✅ 用 useState:变量改变 → 页面 UI 需要跟着变化。值变化需要刷新视图。
- ✅ 用 useRef:变量只做内部记忆,变化不需要更新界面;存定时器 id、DOM 实例、计数器、临时标记。
最终结论
写组件的时候,先区分变与不变:
如果一份数据不再影响 UI 渲染,就不要再继续占用 state,优先考虑切换为 ref。 state 是为界面渲染服务的,不是所有会变的数据,都要写成 state。
补充小提醒
- ref 的更新是同步的,修改
.current,不会触发重新渲染; - state 更新是异步批处理,目的就是为了驱动视图刷新;
- 不要把所有会变的变量无脑丢进
useState,会带来很多不必要的重复渲染。
发表回复