React-核心Hook:useRef

36 阅读2分钟

前言

在 React 的声明式编程中,我们习惯于通过“状态驱动视图”。但在某些场景下,我们需要直接操作 DOM,或者存储一些不希望触发页面重新渲染的数据。这时,useRef 就成了不可或缺的利器。

一、 核心概念:什么是 useRef?

useRef 会返回一个可变的 ref 对象,其内部只有一个 current 属性。

  • 持久性:这个 ref 对象在组件的整个生命周期内保持不变,即使组件多次重新渲染,ref.current 也会指向同一个引用。
  • 静默性修改 current 的值不会触发组件的重新渲染。这与 useState 有本质区别。

语法参考

const myRef = useRef<T>(initialValue);
  • initialValuecurrent 属性的初始值。该值仅在首次渲染时生效,后续渲染会被忽略。

二、 三大经典使用场景

1. 获取 DOM 元素并调用原生 API

这是最常用的场景。通过将 ref 属性绑定到 JSX 标签上,可以直接访问真实的 DOM。

import React, { useRef, useEffect } from 'react';

const AutoFocusInput: React.FC = () => {
  // 定义 HTMLInputElement 类型的 ref
  const inputRef = useRef<HTMLInputElement>(null);

  const handleFocus = () => {
    // 通过 current 访问原生 DOM API
    inputRef.current?.focus();
    if (inputRef.current) {
      inputRef.current.style.border = "2px solid blue";
    }
  };

  return (
    <div style={{ padding: '20px' }}>
      <input ref={inputRef} type="text" placeholder="点击按钮聚焦" />
      <button onClick={handleFocus}>手动聚焦</button>
    </div>
  );
};

export default AutoFocusInput;

2. 保存定时器或外部监听器的引用

如果将定时器 ID 存入 useState,每次修改都会导致组件重绘。使用 useRef 可以在不影响渲染的情况下保存这些引用,并在卸载时安全清除。

import React, { useRef, useEffect } from 'react';

const Timer: React.FC = () => {
  const timerIdRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    // 保存定时器 ID
    timerIdRef.current = setInterval(() => {
      console.log('数据上报中...');
    }, 1000);

    // 组件卸载时,利用 ref 准确清除
    return () => {
      if (timerIdRef.current) {
        clearInterval(timerIdRef.current);
      }
    };
  }, []);

  return <div>后台监控运行中,请查看控制台...</div>;
};

export default Timer;

3. 记录“上一次”的状态值(对比前后变化)

由于 useRef 不随渲染更新,我们可以利用它捕捉渲染快照,用于对比逻辑。

import React, { useState, useEffect, useRef } from 'react';

const PrevStateDemo: React.FC = () => {
  const [count, setCount] = useState<number>(0);
  const prevCountRef = useRef<number>(0);

  useEffect(() => {
    // 渲染完成后,将当前的 count 存入 ref,供下一次渲染对比
    prevCountRef.current = count;
  }, [count]);

  return (
    <div>
      <h1>当前值: {count}</h1>
      <h2>上一次的值: {prevCountRef.current}</h2>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
    </div>
  );
};

export default PrevStateDemo;