两个用途

// 1. 拿 DOM 节点
const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />
inputRef.current?.focus();

// 2. 存任意值(不触发 re-render)
const idRef = useRef(0);
idRef.current++;        // 改它不会让组件重新渲染

ref vs state

state ref
变化触发 re-render
渲染中读 不变(render 内是快照) 总是最新
适合 UI 显示的值 不需要显示、需要跨 render 存的值

判断标准:这个值变了 UI 要更新吗?要 → state;不要 → ref。

常见 ref 场景

计时器 ID

const timerRef = useRef<NodeJS.Timeout | null>(null);

const start = () => {
  timerRef.current = setInterval(...);
};
const stop = () => {
  if (timerRef.current) clearInterval(timerRef.current);
};

保存上一个值

function usePrevious<T>(value: T) {
  const ref = useRef<T>();
  useEffect(() => { ref.current = value; });
  return ref.current;
}

避免重复执行

const initedRef = useRef(false);
useEffect(() => {
  if (initedRef.current) return;
  initedRef.current = true;
  // 真正只想跑一次的初始化
}, []);

最新值(解 stale closure)

const valueRef = useRef(value);
useEffect(() => { valueRef.current = value; });
// 在回调里读 valueRef.current 总是最新

拿 DOM 操作的常见动作

inputRef.current?.focus();
inputRef.current?.scrollIntoView();
inputRef.current?.select();
videoRef.current?.play();
canvasRef.current?.getContext('2d');

forwardRef

子组件转发 ref 给内部 DOM:

const Input = forwardRef<HTMLInputElement, Props>((props, ref) => {
  return <input ref={ref} {...props} />;
});

// 父组件
<Input ref={myRef} />

React 19+:函数组件直接可以接收 ref 作为普通 prop——不再需要 forwardRef 包装。新项目优先用 19+ 写法。

useImperativeHandle

让父组件能调子组件暴露的方法:

const FancyInput = forwardRef((props, ref) => {
  const localRef = useRef<HTMLInputElement>(null);

  useImperativeHandle(ref, () => ({
    focus: () => localRef.current?.focus(),
    clear: () => { if (localRef.current) localRef.current.value = ''; },
  }));

  return <input ref={localRef} />;
});

// 父组件
const r = useRef<{ focus: () => void; clear: () => void }>(null);
r.current?.focus();

慎用——大部分时候用 props + state 就行。它是命令式接口,逆 React 数据流。

  • render 中读写 ref:技术上能跑,但是反模式。ref 操作放在事件回调或 effect 里。
  • ref 的初始值useRef(initial) 在每次 render 都创建对象,但只在第一次保留。给 ref 传函数没用——它不会调用。
  • 回调 ref<div ref={(node) => { ... }} />——节点变化时调用,可以做更细的控制。

实战例子:点击外部关闭

function useClickOutside(handler: () => void) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const onClick = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) handler();
    };
    document.addEventListener('mousedown', onClick);
    return () => document.removeEventListener('mousedown', onClick);
  }, [handler]);
  return ref;
}

// 用法
const popoverRef = useClickOutside(() => setOpen(false));
<div ref={popoverRef}>...</div>

→ 下一篇 useMemo / useCallback / memo