详解 React 中的 ref

ref 是什么

  • 在 React 中,ref 主要用于获取组件或 DOM 节点的引用,也可以用于组件间通信。它是一个普通的 JavaScript 对象,拥有一个名为 current 的属性。
  • 它的创建方式有两种,createRef 和 useRef。

createRef

调用 createRef 在类组件中声明一个 ref,createRef 不接收任何参数。调用 createRef 声明的 ref 会在组件每次渲染的时候重新创建,每次渲染都会返回一个新的引用。

scala 复制代码
import { Component, createRef } from 'react';

export default class Form extends Component {
  inputRef = createRef();

  handleClick = () => {
    this.inputRef.current.focus();
  }

  render() {
    return (
      <>
        <input ref={this.inputRef} />
        <button onClick={this.handleClick}>
          输入框聚焦
        </button>
      </>
    );
  }
}

useRef

函数组件中使用 useRef 来创建 ref。接收一个任意初始值,如字符串、对象,甚至是函数等。使用 useRef 创建的 ref 只会在组件首次渲染时创建,每次都会返回相同的引用,所以通过 ref 可以拿到最新的值。

scss 复制代码
useRef(initialValue) returns { current: initialValue }
javascript 复制代码
import { useState, useRef } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const textRef = useRef(text);

  function handleChange(e) {
    setText(e.target.value);
    textRef.current = e.target.value;
  }

  function handleSend() {
    setTimeout(() => {
      alert('Sending: ' + textRef.current);
    }, 3000);
  }

  return (
    <>
      <input
        value={text}
        onChange={handleChange}
      />
      <button
        onClick={handleSend}>
        发送
      </button>
      <div>{text}</div>
    </>
  );
}

上面例子中,输入文本后点击发送,alert 会在3秒后显示,在这期间,如果修改文本内容,alert 会显示修改后的文本内容。

const ref = useRef() 相当于 const [ref, _] = useState(() => createRef(null))

什么时候用 ref

When you want a component to "remember" some information, but you don't want that information to trigger new renders, you can use a ref. 当您希望组件"记住"某些信息,但不希望该信息触发新的渲染时,可以使用 ref。

ref 的使用场景

存储定时器 ID

javascript 复制代码
import { useState, useRef } from 'react';

export default function Chat() {
  const [text, setText] = useState('');
  const [isSending, setIsSending] = useState(false);
  // let timeoutID = null;
  const timeoutRef = useRef(null);

  function handleSend() {
    setIsSending(true);
    // timeoutID = setTimeout(() => {
    //   alert('Sent!');
    //   setIsSending(false);
    //  }, 3000);
    timeoutRef.current = setTimeout(() => {
      alert('hahaha!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    // clearTimeout(timeoutID);
    clearTimeout(timeoutRef.current);
  }

  return (
    <>
      <input
        disabled={isSending}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button
        disabled={isSending}
        onClick={handleSend}>
        {isSending ? '发送中...' : '发送'}
      </button>
      {isSending &&
        <button onClick={handleUndo}>
          取消
        </button>
      }
    </>
  );
}

上面例子输入文本后点击发送按钮,3秒后才会 alert 文本内容,点击取消按钮可以取消 alert 的弹出。

存储和操作 DOM 节点

javascript 复制代码
import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        输入框聚焦
      </button>
    </>
  );
}

上面例子是通过点击按钮让输入框聚焦

javascript 复制代码
import { useRef } from 'react';

export default function CatFriends() {
  const firstCatRef = useRef(null);
  const secondCatRef = useRef(null);
  const thirdCatRef = useRef(null);

  function handleScrollToFirstCat() {
    firstCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleScrollToSecondCat() {
    secondCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function handleScrollToThirdCat() {
    thirdCatRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  return (
    <>
      <nav>
        <button onClick={handleScrollToFirstCat}>
          Tom
        </button>
        <button onClick={handleScrollToSecondCat}>
          Maru
        </button>
        <button onClick={handleScrollToThirdCat}>
          Jellylorum
        </button>
      </nav>
      <div>
        <ul>
          <li>
            <img
              src="https://placekitten.com/g/200/200"
              alt="Tom"
              ref={firstCatRef}
            />
          </li>
          <li>
            <img
              src="https://placekitten.com/g/300/200"
              alt="Maru"
              ref={secondCatRef}
            />
          </li>
          <li>
            <img
              src="https://placekitten.com/g/250/200"
              alt="Jellylorum"
              ref={thirdCatRef}
            />
          </li>
        </ul>
      </div>
    </>
  );
}

上面这个例子的按钮通过在相应的 DOM 节点上调用浏览器的 rollIntoView() 方法来将图像居中。

用于组件间的通信

访问子组件的方法

假设你有一个 ChildComponent,它有一个可以被父组件调用的方法 childMethod。

scala 复制代码
// ChildComponent.js
import React from 'react';

class ChildComponent extends React.Component {
  childMethod() {
    console.log('Child method called');
  }

  render() {
    return <div>子组件</div>;
  }
}

父组件可以使用 ref 来调用这个方法:

javascript 复制代码
// ParentComponent.js
import React, { useRef } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const childRef = useRef();

  const callChildMethod = () => {
    childRef.current.childMethod();
  };

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={callChildMethod}>调用子组件方法</button>
    </div>
  );
}

控制子组件的状态

如果子组件有内部状态或行为需要从父组件触发,可以通过 ref 实现。

javascript 复制代码
// ChildComponent.js
import { useState, forwardRef, useImperativeHandle } from 'react';

const ChildComponent = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  // useImperativeHandle 钩子用于自定义 ref 暴露给父组件的实例值。它应与 forwardRef 一起使用。
  useImperativeHandle(ref, () => ({
    incrementCount() {
      setCount((prevCount) => prevCount + 1);
    },
  }));

  return <div>Count: {count}</div>;
});

父组件可以控制子组件的状态

javascript 复制代码
// ParentComponent.js
import { useRef } from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const childRef = useRef();

  return (
    <div>
      <ChildComponent ref={childRef} />
      <button onClick={() => childRef.current.incrementCount()}>加</button>
    </div>
  );
}

注意:不推荐使用 ref 代替 props 和 state 来管理组件间的数据流

ref 和 state 的区别

注意事项

参考文档: react 官网

相关推荐
子兮曰7 小时前
async/await高级模式:async迭代器、错误边界与并发控制
前端·javascript·github
恋猫de小郭7 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
GIS之路9 小时前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒10 小时前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
gxp12311 小时前
初学React:请求数据参数未更新 && 数据异步状态更新问题
react.js
Kagol11 小时前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉11 小时前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau11 小时前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生11 小时前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼11 小时前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范