React hook之useRef

React useRef 详解

useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。

基本概念

1. 创建 ref

jsx 复制代码
const refContainer = useRef(initialValue);
  • initialValue: ref 对象的初始值(.current 属性的初始值)
  • 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数

2. 核心特性

  • 跨渲染周期保存值:ref 对象在组件的整个生命周期内保持不变
  • 修改不会触发重新渲染 :改变 .current 属性不会导致组件重新渲染
  • 直接访问 DOM 元素:最常见的用途之一

主要用途

1. 访问 DOM 元素

最常见的用法是访问 JSX 渲染的 DOM 元素:

jsx 复制代码
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

2. 存储可变值

可以存储任何可变值,类似于类组件中的实例属性:

jsx 复制代码
function Timer() {
  const intervalRef = useRef();
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      console.log('Timer tick');
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);
  
  // ...
}

3. 保存上一次的值

实现获取上一次 props 或 state 的功能:

jsx 复制代码
function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    prevCountRef.current = count;
  });
  
  const prevCount = prevCountRef.current;
  
  return (
    <div>
      <p>Current: {count}, Previous: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

4. 避免重复执行 useEffect

解决 React 18+ 严格模式下 useEffect 执行两次的问题:

jsx 复制代码
useEffect(() => {
  const executedRef = useRef(false);
  
  if (!executedRef.current) {
    executedRef.current = true;
    // 你的初始化代码
  }
}, []);

高级用法

1. 转发 refs (forwardRef)

将 ref 传递给子组件:

jsx 复制代码
const FancyInput = React.forwardRef((props, ref) => {
  return <input ref={ref} className="fancy-input" {...props} />;
});

function App() {
  const inputRef = useRef();
  
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  
  return <FancyInput ref={inputRef} />;
}

2. 回调 refs

动态设置多个 ref:

jsx 复制代码
function MeasureExample() {
  const [height, setHeight] = useState(0);
  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);
  
  return (
    <div ref={measuredRef}>
      <h1>Hello, world</h1>
      <p>The above header is {Math.round(height)}px tall</p>
    </div>
  );
}

3. 与第三方 DOM 库集成

jsx 复制代码
function Canvas() {
  const canvasRef = useRef(null);
  
  useEffect(() => {
    const ctx = canvasRef.current.getContext('2d');
    // 使用第三方库绘制
    new ThirdPartyLibrary(ctx);
  }, []);
  
  return <canvas ref={canvasRef} />;
}

useRef 与 useState 的区别

特性 useRef useState
触发重新渲染
值更新时机 同步 异步
适合存储 DOM 引用、可变变量、计时器 需要触发 UI 更新的状态
访问方式 .current 属性 直接访问状态变量
初始化 参数作为 .current 初始值 参数作为初始状态

注意事项

  1. 不要在渲染期间写入/读取 ref.current

    jsx 复制代码
    // 错误示例
    function MyComponent() {
      const myRef = useRef();
      myRef.current = 123; // 不应该在渲染期间修改
      return <div>{myRef.current}</div>; // 也不应该依赖渲染期间的值
    }
  2. ref 不会自动通知内容变化

    • 如果需要在 ref 变化时执行代码,使用回调 ref 或手动监听
  3. 多个 refs 合并

    jsx 复制代码
    function useCombinedRefs(...refs) {
      return useCallback(el => {
        refs.forEach(ref => {
          if (!ref) return;
          if (typeof ref === 'function') ref(el);
          else ref.current = el;
        });
      }, refs);
    }
  4. 服务端渲染(SSR)注意事项

    • ref 在服务端渲染时不会被序列化
    • 在服务端和客户端渲染结果要保持一致

性能优化

useRef 本身是轻量级的,但以下情况需要注意:

  1. 避免在渲染函数中创建新 ref

    jsx 复制代码
    // 不好 - 每次渲染都创建新 ref
    function Component() {
      return <Child ref={useRef()} />;
    }
    
    // 好 - ref 只创建一次
    function Component() {
      const ref = useRef();
      return <Child ref={ref} />;
    }
  2. 大量 ref 的内存问题

总结

useRef 是 React Hooks 中一个非常实用的工具,它:

  • 提供了一种访问 DOM 节点的方式
  • 可以存储不会触发重新渲染的可变值
  • 在组件的整个生命周期内保持引用不变
  • 是集成第三方库和实现高级模式的利器
相关推荐
我是哈哈hh7 分钟前
【Node.js】ECMAScript标准 以及 npm安装
开发语言·前端·javascript·node.js
张元清29 分钟前
电商 Feeds 流缓存策略:Temu vs 拼多多的技术选择
前端·javascript·面试
一枚前端小能手30 分钟前
🎨 CSS布局从入门到放弃?Grid让你重新爱上布局
前端·css
晴空雨31 分钟前
React 合成事件原理:从事件委托到 React 17 的重大改进
前端·react.js
魏嗣宗33 分钟前
Node.js 网络编程全解析:从 Socket 到 HTTP,再到流式协议
前端·全栈
pepedd86434 分钟前
还在开发vue2老项目吗?本文带你梳理vue版本区别
前端·vue.js·trae
pepedd8641 小时前
浅谈js拷贝问题-解决拷贝数据难题
前端·javascript·trae
@大迁世界1 小时前
useCallback 的陷阱:当 React Hooks 反而拖了后腿
前端·javascript·react.js·前端框架·ecmascript
跟橙姐学代码1 小时前
学Python别死记硬背,这份“编程生活化笔记”让你少走三年弯路
前端·python
前端缘梦1 小时前
深入理解 Vue 中的虚拟 DOM:原理与实战价值
前端·vue.js·面试