跳到主要内容

ref 引用值

ref 保存的是什么?​

  • 可变容器: useRef(initialValue) 返回具有 current 字段的对象,同一组件的后续渲染得到同一个对象;
  • 不触发渲染: 修改 current 不会通知 React,因此适合定时器 ID、DOM 节点等不直接决定 UI 的信息;
  • 初始参数: 只用于初始化容器,后续传入不同初始值不会自动替换 current;

怎样选择 state 或 ref?​

需求选择原因
显示点击次数state变化需要更新 UI
保存定时器 IDref只为以后清理使用
保存 DOM 节点ref需要调用浏览器方法
渲染期间临时计算局部变量不需要跨渲染保存

如何在事件之间保存一个可变值?​

  • 读写位置: 在事件或 Effect 中读写 ref,把它作为外部操作的句柄,而不是通过它隐藏应该进入 state 的数据;
  • 生命周期: ref 只随组件实例存活,组件卸载后需要长期保留的数据应放到更稳定的位置;
import { useRef } from "react";

function DelayedAlert() {
const timeoutRef = useRef(null);
function schedule() {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => alert("完成"), 1000);
}
return <button onClick={schedule}>稍后提示</button>;
}
  • 清理责任: 上例只演示事件间保存句柄;需要随卸载取消的任务,应额外由 Effect 清理,完整定时器生命周期见闭包与定时器;
// 放在持有 timeoutRef 的组件顶层,并导入 useEffect。
useEffect(() => {
return () => {
clearTimeout(timeoutRef.current);
};
}, []);

为什么渲染时通常不能读写 current?​

  • 纯度要求: ref 的变更不会进入 React 的快照机制,渲染中读写会让结果依赖可变外部时序,在重试时产生不一致;
  • 初始化例外: 可预测且只在 current 为空时执行的初始化可以使用,详细写法见 Context 与 Ref 参考;
  • 不可替代: UI 要显示的值应使用 state,不能通过强制重渲染补救以 ref 代替 state 的设计;