1. 基本概念
useRef 是 React 的一个 Hook,返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。这个对象在组件的整个生命周期内保持不变。
2. 主要用途和特性
2.1 获取 DOM 元素实例
            
            
              jsx
              
              
            
          
          function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    // 直接访问 DOM 元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>聚焦输入框</button>
    </>
  );
}
        2.2 存储组件渲染周期之间的共享数据
- useRef 只会在组件初始化时执行一次
 - state 改变引起的重新渲染不会导致 useRef 重新执行
 - 适合存储不需要触发视图更新的数据
 
            
            
              jsx
              
              
            
          
          function Counter() {
  const [count, setCount] = useState(0);
  const renderCount = useRef(0);  // 用于记录渲染次数
  
  useEffect(() => {
    renderCount.current += 1;
    console.log(`组件已渲染 ${renderCount.current} 次`);
  });
  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}
        2.3 useRef 的重要特性
- current 值的修改不会触发重新渲染
 
            
            
              jsx
              
              
            
          
          function Example() {
  const countRef = useRef(0);
  
  const handleClick = () => {
    // 修改 ref 不会导致组件重新渲染
    countRef.current += 1;
    console.log('当前值:', countRef.current);
  };
  return <button onClick={handleClick}>点击</button>;
}
        - 不应作为其他 Hooks 的依赖项
 
            
            
              jsx
              
              
            
          
          function BadExample() {
  const valueRef = useRef(0);
  
  // ❌ 错误示例
  useEffect(() => {
    console.log(valueRef.current);
  }, [valueRef.current]); // 不要这样做
}
function GoodExample() {
  const valueRef = useRef(0);
  
  // ✅ 正确示例
  useEffect(() => {
    console.log(valueRef.current);
  }); // 不将 ref 作为依赖项
}
        3. forwardRef 和 useImperativeHandle
3.1 基本用法示例
            
            
              jsx
              
              
            
          
          // CustomInput.jsx
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    // 只暴露需要的方法
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));
  return <input ref={inputRef} {...props} />;
});
// Parent.jsx
function Parent() {
  const inputRef = useRef();
  const handleClick = () => {
    inputRef.current.focus();
    console.log(inputRef.current.getValue());
  };
  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleClick}>操作输入框</button>
    </div>
  );
}
        3.2 复杂组件示例(不同粒度的暴露)
            
            
              jsx
              
              
            
          
          const ComplexComponent = forwardRef((props, ref) => {
  const inputRef = useRef();
  const checkboxRef = useRef();
  const formRef = useRef();
  useImperativeHandle(ref, () => ({
    // 粒度级别 1:表单级操作
    form: {
      reset: () => {
        inputRef.current.value = '';
        checkboxRef.current.checked = false;
      },
      validate: () => {
        return inputRef.current.value.length > 0;
      }
    },
    
    // 粒度级别 2:具体输入框操作
    input: {
      focus: () => inputRef.current.focus(),
      getValue: () => inputRef.current.value,
      setValue: (value) => {
        inputRef.current.value = value;
      }
    },
    
    // 粒度级别 3:简单方法
    clear: () => {
      inputRef.current.value = '';
    }
  }));
  return (
    <form ref={formRef}>
      <input ref={inputRef} type="text" />
      <input ref={checkboxRef} type="checkbox" />
    </form>
  );
});
// 使用示例
function ComplexParent() {
  const componentRef = useRef();
  const handleOperations = () => {
    // 使用不同粒度的操作
    componentRef.current.form.reset();
    componentRef.current.input.focus();
    componentRef.current.input.setValue('新值');
    componentRef.current.clear();
    
    if (componentRef.current.form.validate()) {
      console.log('表单验证通过');
    }
  };
  return (
    <div>
      <ComplexComponent ref={componentRef} />
      <button onClick={handleOperations}>执行操作</button>
    </div>
  );
}
        4. 注意事项
- useRef 不能直接引用函数式组件,必须配合 forwardRef 使用
 - useRef 的值改变不会触发重新渲染,如果需要在值改变时重新渲染,应使用 useState
 - 使用 useImperativeHandle 时,应该只暴露必要的方法,保持良好的封装性
 - 避免在 render 过程中读取或写入 ref.current
 
5. 最佳实践
- 使用 TypeScript 定义暴露的接口类型
 - 合理划分暴露方法的粒度
 - 文档化暴露的方法
 - 遵循最小暴露原则
 - 在清理阶段(cleanup)正确处理 ref,特别是涉及定时器等资源时
 
6. 使用场景建议
- 访问 DOM 元素或组件实例
 - 存储定时器 ID
 - 存储上一次的值
 - 存储不需要触发重新渲染的数据
 - 跨组件方法调用(通过 forwardRef)
 
通过合理使用 useRef,可以优化组件性能,实现更复杂的组件交互,同时保持代码的可维护性和可读性。