Skip to content

使用ref引用值

当你希望组件“记住”某些信息,但又不想让这些信息 触发新的渲染 时,你可以使用 ref

给你的组件添加 ref 

你可以通过从 React 导入 useRef Hook 来为你的组件添加一个 ref:

js
import { useRef } from 'react';

在你的组件内,调用 useRef Hook 并传入你想要引用的初始值作为唯一参数。例如,这里的 ref 引用的值是“0”:

js
const ref = useRef(0);

useRef 返回一个这样的对象:

js
{   
	current: 0 // 你向 useRef 传入的值
}

这里的 ref 指向一个数字,但是,像 state 一样,你可以让它指向任何东西:字符串、对象,甚至是函数。与 state 不同的是,ref 是一个普通的 JavaScript 对象,具有可以被读取和修改的 current 属性。

请注意,组件不会在每次递增时重新渲染。 与 state 一样,React 会在每次重新渲染之间保留 ref。但是,设置 state 会重新渲染组件,更改 ref 不会!

ref 和 state 的不同之处 

也许你觉得 ref 似乎没有 state 那样“严格” —— 例如,你可以改变它们而非总是必须使用 state 设置函数。但在大多数情况下,我们建议你使用 state。ref 是一种“脱围机制”,你并不会经常用到它。 以下是 state 和 ref 的对比:

refstate
useRef(initialValue)返回 { current: initialValue }useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue])
更改时不会触发重新渲染更改时触发重新渲染。
可变 —— 你可以在渲染过程之外修改和更新 current 的值。“不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。
你不应在渲染期间读取(或写入) current 值。你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照

尽管 useStateuseRef 都是由 React 提供的,原则上 useRef 可以在 useState 的基础上 实现。 你可以想象在 React 内部,useRef 是这样实现的:

js
// React 内部
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}

第一次渲染期间,useRef 返回 { current: initialValue }。 该对象由 React 存储,因此在下一次渲染期间将返回相同的对象。 请注意,在这个示例中,state 设置函数没有被用到。它是不必要的,因为 useRef 总是需要返回相同的对象!

React 提供了一个内置版本的 useRef,因为它在实践中很常见。 但是你可以将其视为没有设置函数的常规 state 变量。 如果你熟悉面向对象编程,ref 可能会让你想起实例字段 —— 但是你写的不是 this.something,而是 somethingRef.current

何时使用 ref 

通常,当你的组件需要“跳出” React 并与外部 API 通信时,你会用到 ref —— 通常是不会影响组件外观的浏览器 API。以下是这些罕见情况中的几个:

如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。

最佳实践

在这里的timeoutID使用了let来声明,那么在组件重新渲染后,它的值就会重新赋值为null,也就是丢失了发送出去的定时器的ID,这里我们可以使用ref来存储这些不会导致页面更新的数据:

js
import { useState } from 'react';

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

  function handleSend() {
    setIsSending(true);
    timeoutID = setTimeout(() => {
      alert('已发送!');
      setIsSending(false);
    }, 3000);
  }

  function handleUndo() {
    setIsSending(false);
    clearTimeout(timeoutID);
  }

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