v19.2Latest

使用 Refs 引用數值

當你想要一個元件「記住」某些資訊,但又不希望該資訊觸發新的渲染時,你可以使用ref

您將學習
  • 如何為你的元件添加 ref
  • 如何更新 ref 的數值
  • ref 與 state 有何不同
  • 如何安全地使用 refs

為你的元件添加 ref

你可以透過從 React 匯入useRefHook 來為元件添加 ref:

在你的元件內部,呼叫useRef Hook 並將你想要引用的初始數值作為唯一參數傳入。例如,這是一個數值為0的 ref:

useRef會回傳一個像這樣的物件:

一支寫著 'current' 的箭頭塞進一個寫著 'ref' 的口袋裡。

插圖:Rachel Lee Nabors

你可以透過ref.current屬性存取該 ref 的當前數值。這個數值被設計為可變的,意味著你可以讀取和寫入它。它就像是元件的一個秘密口袋,React 不會追蹤它。(這就是它成為 React 單向資料流的「逃生艙口」的原因——更多詳情見下文!)

以下範例中,一個按鈕將在每次點擊時遞增ref.current

該 ref 指向一個數字,但就像state一樣,你也可以指向任何東西:字串、物件、甚至函數。與 state 不同的是,ref 是一個普通的 JavaScript 物件,帶有一個你可以讀取和修改的current屬性。

請注意元件不會隨著每次遞增而重新渲染。與 state 一樣,refs 會在重新渲染之間被 React 保留。然而,設定 state 會觸發元件重新渲染。改變 ref 則不會!

範例:建立一個碼錶

你可以在單個元件中結合使用 refs 和 state。例如,讓我們建立一個碼錶,使用者可以透過按下按鈕來啟動或停止。為了顯示自使用者按下「開始」以來經過了多少時間,你需要追蹤開始按鈕被按下的時間以及當前時間。此資訊用於渲染,因此你將它保存在 state 中:

當使用者按下「開始」時,你將使用setInterval以便每 10 毫秒更新一次時間:

當按下「停止」按鈕時,你需要取消現有的間隔,使其停止更新now狀態變數。你可以透過呼叫clearInterval來實現,但你需要提供先前使用者按下開始時由setInterval呼叫所回傳的間隔 ID。你需要將這個間隔 ID 儲存在某處。由於間隔 ID 不用於渲染,你可以將它保存在一個 ref 中:

當一段資訊用於渲染時,請將其保存在狀態中。當一段資訊僅被事件處理器需要,且更改它不需要重新渲染時,使用引用(ref)可能更有效率。

引用與狀態之間的差異

或許你會覺得引用看起來比狀態「不嚴格」——例如,你可以直接改變它們,而不總是必須使用狀態設置函數。但在大多數情況下,你會希望使用狀態。引用是一種你通常不太需要的「逃生艙口」。以下是狀態和引用的比較:

引用(refs)狀態(state)
useRef(initialValue) 返回 { current: initialValue }useState(initialValue) 返回一個狀態變數的當前值以及一個狀態設置函數([value, setValue]
當你更改它時不會觸發重新渲染。當你更改它時會觸發重新渲染。
可變的——你可以在渲染過程之外修改並更新current 的值。「不可變的」——你必須使用狀態設置函數來修改狀態變數,以排隊進行重新渲染。
你不應該在渲染期間讀取(或寫入)current 值。你可以隨時讀取狀態。然而,每次渲染都有其自己的、不會改變的狀態快照

以下是一個使用狀態實現的計數器按鈕:

因為 count 值會顯示出來,所以為它使用狀態值是合理的。當使用 setCount()設置計數器的值時,React 會重新渲染組件,螢幕也會更新以反映新的計數。

如果你嘗試用引用來實現這個功能,React 永遠不會重新渲染組件,所以你永遠不會看到計數變化!請看點擊這個按鈕並不會更新其文字

這就是為什麼在渲染期間讀取ref.current會導致不可靠的程式碼。如果你需要這樣做,請改用狀態。

Deep Dive
useRef內部如何運作?

何時使用 refs

通常,當你的元件需要「跳出」React 並與外部 API(通常是瀏覽器 API)進行通訊時,你會使用 ref,這通常不會影響元件的外觀。以下是幾種罕見的情況:

如果你的元件需要儲存一些值,但它不影響渲染邏輯,請選擇 refs。

Refs 的最佳實踐

遵循這些原則將使你的元件行為更可預測:

  • 將 refs 視為一個逃生艙口。當你與外部系統或瀏覽器 API 協作時,refs 很有用。如果你的應用程式邏輯和資料流在很大程度上依賴於 refs,你可能需要重新思考你的方法。
  • 不要在渲染期間讀取或寫入ref.current如果某些資訊需要在渲染期間使用,請改用狀態。由於 React 不知道ref.current 何時改變,即使在渲染期間讀取它也會使你的元件行為難以預測。(唯一的例外是像 if (!ref.current) ref.current = new Thing()這樣的程式碼,它只在首次渲染期間設定 ref 一次。)

React 狀態的限制不適用於 refs。例如,狀態在每次渲染時的行為都像一個快照,並且不會同步更新。但是當你修改 ref 的 current 值時,它會立即改變:

這是因為 ref 本身就是一個普通的 JavaScript 物件,所以它的行為也像一個。

當你處理 ref 時,你也不需要擔心避免突變。只要你正在突變的物件不用於渲染,React 就不關心你對 ref 或其內容做了什麼。

Refs 和 DOM

你可以將 ref 指向任何值。然而,ref 最常見的使用場景是存取一個 DOM 元素。例如,如果你想以程式設計方式聚焦一個輸入框,這會很方便。當你將 ref 傳遞給 JSX 中的ref 屬性時,例如 <div ref={myRef}>,React 會將相應的 DOM 元素放入myRef.current。一旦元素從 DOM 中移除,React 會將myRef.current更新為null。你可以在 使用 Refs 操作 DOM 中閱讀更多相關內容。

總結

  • Refs 是一個用來保留與渲染無關的值的逃生艙口,您不會經常用到它們。
  • Ref 是一個普通的 JavaScript 物件,它有一個名為current的屬性,您可以讀取或設定它。
  • 您可以通過呼叫 useRefHook 來要求 React 給您一個 ref。
  • 與狀態類似,refs 能讓您在元件重新渲染之間保留資訊。
  • 與狀態不同的是,設定 ref 的current 值不會觸發重新渲染。
  • 不要在渲染期間讀取或寫入ref.current,這會使您的元件行為難以預測。

嘗試一些挑戰

Challenge 1 of 4:修復故障的聊天輸入 #

輸入一則訊息並點擊「發送」。您會注意到在顯示「已發送!」提示前有三秒延遲。在這段延遲期間,您可以看到一個「復原」按鈕。點擊它。這個「復原」按鈕應該會阻止「已發送!」訊息出現。它是透過呼叫 clearTimeout 來清除在 handleSend 期間儲存的計時器 ID 來實現的。然而,即使點擊了「復原」,「已發送!」訊息仍然會出現。找出它無效的原因,並修復它。