react中hooks之useRef 用法总结

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 的重要特性

  1. current 值的修改不会触发重新渲染
jsx 复制代码
function Example() {
  const countRef = useRef(0);
  
  const handleClick = () => {
    // 修改 ref 不会导致组件重新渲染
    countRef.current += 1;
    console.log('当前值:', countRef.current);
  };

  return <button onClick={handleClick}>点击</button>;
}
  1. 不应作为其他 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. 注意事项

  1. useRef 不能直接引用函数式组件,必须配合 forwardRef 使用
  2. useRef 的值改变不会触发重新渲染,如果需要在值改变时重新渲染,应使用 useState
  3. 使用 useImperativeHandle 时,应该只暴露必要的方法,保持良好的封装性
  4. 避免在 render 过程中读取或写入 ref.current

5. 最佳实践

  1. 使用 TypeScript 定义暴露的接口类型
  2. 合理划分暴露方法的粒度
  3. 文档化暴露的方法
  4. 遵循最小暴露原则
  5. 在清理阶段(cleanup)正确处理 ref,特别是涉及定时器等资源时

6. 使用场景建议

  1. 访问 DOM 元素或组件实例
  2. 存储定时器 ID
  3. 存储上一次的值
  4. 存储不需要触发重新渲染的数据
  5. 跨组件方法调用(通过 forwardRef)

通过合理使用 useRef,可以优化组件性能,实现更复杂的组件交互,同时保持代码的可维护性和可读性。

相关推荐
福兮说8 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理9 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6669 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人10 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)10 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan10 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_11 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒11 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技11 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu12 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub