第二章前置知识:2.6 useRef 基础知识

147 阅读5分钟

本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️

React18 源码系列会随着学习 React 源码的实时进度而实时更新:约,两天一小改,五天一大改。

useRef是什么

useRef是一个 React Hook,可让您引用渲染不需要的值

const ref = useRef(initialValue)

在组件的顶层调用useRef来声明引用

import { useRef } from 'react';

function MyComponent() {
  const intervalRef = useRef(0);
  const inputRef = useRef(null);
  // ...

参数:

initialValue :您希望 ref 对象的current属性最初的值。它可以是任何类型的值。初始渲染后该参数将被忽略。

返回值:

useRef返回一个具有单个属性的对象:current :最初,它设置为您传递的initialValue 。您稍后可以将其设置为其他内容。如果将 ref 对象作为 JSX 节点的ref属性传递给 React,React 将设置其current属性

在下一次渲染时, useRef将返回相同的对象。

注意事项

您可以改变ref.current属性。与状态不同,它是可变(mutabal)的,但是,如果它包含用于渲染的对象(例如,状态的一部分),那么您不应该改变该对象。

当您更改ref.current属性时,React 不会重新渲染您的组件。 React 不知道你什么时候改变它,因为 ref 是一个普通的 JavaScript 对象。

除了初始化之外,请勿在渲染期间写入或读取ref.current 。这使得组件的行为变得不可预测。

使用方法

用 ref 引用一个值

在组件的顶层调用useRef来声明一个或多个refs.useRef返回一个ref 对象,其中单个current属性最初设置为您提供的初始值

import { useRef } from 'react';

function Stopwatch() {
  const intervalRef = useRef(0);
  // ...

在下一次渲染时, useRef将返回相同的对象.您可以更改其current属性以存储信息并稍后读取

更改ref不会触发重新渲染。 这意味着 refs 非常适合存储不影响组件视觉输出的信息

例如,如果您需要存储interval ID并稍后检索它,则可以将其放入 ref 中。要更新 ref 内的值,您需要手动更改其current属性:例如,如果您需要存储interval ID并稍后检索它,则可以将其放入 ref 中。要更新 ref 内的值,您需要手动更改其current属性:

function handleStartClick() {
  const intervalId = setInterval(() => {
    // ...
  }, 1000);
  intervalRef.current = intervalId;
}

稍后,您可以从 ref 中读取该interva ID,以便可以调用清除:

function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}

通过使用 ref,您可以确保

  • 您可以在重新渲染之间存储信息(与常规变量不同,常规变量在每次渲染时都会重置)。
  • 更改它不会触发重新渲染(与触发重新渲染的状态变量不同)。
  • 信息对于组件的每个副本来说都是本地的(与外部的变量不同,它们是共享的)

更改 ref 不会触发重新渲染,因此 ref 不适合存储要在屏幕上显示的信息。请使用状态来代替

渲染期间请勿写入或读取ref.current

React 希望组件的主体表现得像一个纯函数

  • 如果输入( propsstatecontext )相同,它应该返回完全相同的 JSX。
  • 以不同的顺序或使用不同的参数调用它不应影响其他调用的结果。

在渲染期间读取或写入 ref 打破了这些期望。

function MyComponent() {
  // ...
  // 🚩 Don't write a ref during rendering
  myRef.current = 123;
  // ...
  // 🚩 Don't read a ref during rendering
  return <h1>{myOtherRef.current}</h1>;
}

您可以从事件处理程序或效果中读取或写入引用

function MyComponent() {
  // ...
  useEffect(() => {
    // ✅ You can read or write refs in effects
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ You can read or write refs in event handlers
    doSomething(myOtherRef.current);
  }
  // ...
}

如果您必须在渲染期间读取或写入某些内容,请改用状态

当你违反这些规则时,你的组件可能仍然可以工作,但是我们添加到 React 中的大多数新功能将依赖于这些期望。阅读有关保持组件纯净的更多信息。

使用 ref 操作 DOM

使用 ref 来操作DOM 是特别常见的。 React 对此有内置支持

首先,声明一个初始值为null的ref 对象:

import { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  // ...

然后将 ref 对象作为ref属性传递给要操作的 DOM 节点的 JSX:

 // ...
  return <input ref={inputRef} />;

React 创建 DOM 节点并将其放在屏幕上后,React 会将 ref 对象的current属性设置为该 DOM 节点。现在您可以访问<input>的 DOM 节点并调用[focus()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)等方法:

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

当节点从屏幕上移除时,React 会将current属性设置回null

避免重新创建ref内容

React 会保存一次初始引用值,并在下一次渲染时忽略它。

function Video() {
  const playerRef = useRef(new VideoPlayer());
  // ...

尽管new VideoPlayer()的结果仅用于初始渲染,但您仍然在每次渲染时调用此函数。如果创建昂贵的对象,这可能会造成浪费。

要解决这个问题,您可以像这样初始化 ref:

function Video() {

  const playerRef = useRef(null);

  if (playerRef.current === null) {

    playerRef.current = new VideoPlayer();

  }

  // ...

通常,在渲染期间写入或读取ref.current是不允许的。但是,在这种情况下没关系,因为结果始终相同,并且条件仅在初始化期间执行,因此它是完全可预测的。

故障排除

我无法获取自定义组件的ref

如果您尝试将ref传递给您自己的组件,如下所示:

const inputRef = useRef(null);



return <MyInput ref={inputRef} />;

您可能会在控制台中收到错误消息:

默认情况下,您自己的组件不会向其中的 DOM 节点公开refs

要解决此问题,请找到您想要引用的组件:

export default function MyInput({ value, onChange }) {

  return (

    <input

      value={value}

      onChange={onChange}

    />

  );

}

然后将其包装在[forwardRef](https://react.dev/reference/react/forwardRef)中,如下所示

import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) => {
  return (
    <input
      value={value}
      onChange={onChange}
      ref={ref}
    />
  );
});

export default MyInput;

然后父组件可以获得对它的引用

参考链接

关于作者

作者:Wandra

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注呀🌹

本专栏致力于每周分享一个项目,如果本文对你有帮助的话,欢迎点赞或者关注☘️