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 不適合儲存您想要在螢幕上顯示的資訊。請改用狀態。閱讀更多關於 選擇 useRef
和 useState
。
點擊計數器範例 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 操作 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 的資訊。
避免重新建立 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
是不允許的。但在這種情況下是可以的,因為結果始終相同,而且條件只在初始化期間執行,因此它是完全可預測的。
深入探討
如果你使用類型檢查器,並且不想總是檢查 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 公開給它們內部的 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 節點 的資訊。