useRef 是一個 React Hook,可讓您參考不需要用於渲染的值。

const ref = useRef(initialValue)

參考

useRef(initialValue)

在元件的最上層呼叫 useRef 來宣告一個 ref。

import { useRef } from 'react';

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

請參閱下面的更多範例。

參數

  • initialValue:您希望 ref 物件的 current 屬性最初的值。它可以是任何類型的值。初始渲染後,這個參數會被忽略。

回傳值

useRef 會回傳一個具有單一屬性的物件

  • current:最初,它會被設定為您傳遞的 initialValue。您之後可以將它設定為其他值。如果您將 ref 物件以 ref 屬性的形式傳遞給 React 作為 JSX 節點,React 將會設定其 current 屬性。

在下一次渲染時,useRef 將會回傳相同的物件。

注意事項

  • 您可以修改 ref.current 屬性。與狀態不同,它是可變的。但是,如果它持有用於渲染的物件(例如,您的狀態的一部分),則不應該修改該物件。
  • 當您更改 ref.current 屬性時,React 不會重新渲染您的元件。React 不知道您何時更改它,因為 ref 是一個普通的 JavaScript 物件。
  • 除了 初始化 之外,請勿在渲染期間*寫入*或*讀取* ref.current。這會使您元件的行為變得難以預測。
  • 在嚴格模式下,React 將會呼叫您的元件函式兩次,以便 幫助您找到意外的副作用。這是僅限開發的行為,不會影響生產環境。每個 ref 物件將會被建立兩次,但其中一個版本將會被捨棄。如果您的元件函式是純粹的(應該如此),這不應該影響行為。

用法

使用 ref 參考值

在組件的頂層呼叫 useRef 來宣告一個或多個 refs

import { useRef } from 'react';

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

useRef 會回傳一個帶有單一 current 屬性ref 物件,該屬性初始值會設定為您提供的 初始值

在後續的渲染中,useRef 會回傳相同的物件。您可以更改它的 current 屬性來儲存資訊並稍後讀取它。這可能會讓您想起 狀態,但有一個重要的區別。

更改 ref 不會觸發重新渲染。 這表示 ref 非常適合儲存不影響組件視覺輸出的資訊。例如,如果您需要儲存一個 間隔 ID 並稍後取回它,您可以將它放在 ref 中。要更新 ref 內的值,您需要手動更改其 current 屬性

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

稍後,您可以從 ref 讀取該間隔 ID,以便您可以呼叫 清除該間隔

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

透過使用 ref,您可以確保:

  • 您可以在重新渲染之間 儲存資訊(不同於一般變數,它們在每次渲染時都會重置)。
  • 更改它 不會觸發重新渲染(不同於狀態變數,它們會觸發重新渲染)。
  • 資訊是組件每個副本的局部資訊(不同於外部變數,它們是共享的)。

更改 ref 不會觸發重新渲染,因此 ref 不適合儲存您想要在螢幕上顯示的資訊。請改用狀態。閱讀更多關於 選擇 useRefuseState

使用 useRef 參考值的範例

點擊計數器範例 1 2:
點擊計數器

此組件使用 ref 來追蹤按鈕被點擊的次數。請注意,在這裡使用 ref 而不是狀態是可以的,因為點擊計數僅在事件處理程式中讀取和寫入。

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

如果您在 JSX 中顯示 {ref.current},則數字在點擊時不會更新。這是因為設定 ref.current 不會觸發重新渲染。用於渲染的資訊應該是狀態。

陷阱

不要在渲染期間寫入*或讀取* ref.current

React 期望您的組件主體 的行為像純函式

  • 如果輸入(屬性狀態上下文)相同,它應該回傳完全相同的 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>;
}

您可以改為從 事件處理程式或效果讀取或寫入 ref。

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 內建支援此功能。

首先,宣告一個 ref 物件,並將其 初始值 設為 null

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() 的方法

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

當節點從螢幕上移除時,React 會將 current 屬性設定回 null

閱讀更多關於 使用 ref 操作 DOM 的資訊。

使用 useRef 操作 DOM 的範例

點擊計數器範例 1 4:
聚焦文字輸入框

在此範例中,點擊按鈕將會聚焦輸入框

import { useRef } from 'react';

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

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

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}


避免重新建立 ref 內容

React 只會儲存初始 ref 值一次,並在後續渲染時忽略它。

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

雖然 new VideoPlayer() 的結果只用於初始渲染,但你仍在每次渲染時都呼叫此函式。如果它建立了昂貴的物件,這可能會造成浪費。

為了

function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...

通常,在渲染期間讀取或寫入 ref.current 是不允許的。但在這種情況下是可以的,因為結果始終相同,而且條件只在初始化期間執行,因此它是完全可預測的。

深入探討

如何在稍後初始化 useRef 時避免 null 檢查

如果你使用類型檢查器,並且不想總是檢查 null,你可以嘗試以下模式

function Video() {
const playerRef = useRef(null);

function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}

// ...

這裡,playerRef 本身可以是 null。但是,你應該能夠讓你的類型檢查器相信,沒有任何情況下 getPlayer() 會返回 null。然後在你的事件處理程式中使用 getPlayer()


疑難排解

我無法取得客製化元件的 ref

如果您嘗試像這樣將 ref 傳遞給您自己的元件

const inputRef = useRef(null);

return <MyInput ref={inputRef} />;

您可能會在控制台中看到錯誤訊息

控制台
警告:函式元件不能被賦予 refs。嘗試訪問此 ref 將會失敗。您是否 meant to use React.forwardRef()?

預設情況下,您自己的元件不會將 refs 公開給它們內部的 DOM 節點。

要解決此問題,請找到您想要取得 ref 的元件

export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}

然後像這樣將它包裝在 forwardRef

import { forwardRef } from 'react';

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

export default MyInput;

然後父元件就可以取得它的 ref。

閱讀更多關於 存取其他元件的 DOM 節點 的資訊。

...(保留前後頁連結的程式碼,因翻譯不影響其功能)