使用 Refs 引用數值
當你想要一個元件「記住」某些資訊,但又不希望該資訊觸發新的渲染時,你可以使用ref。
您將學習
- 如何為你的元件添加 ref
- 如何更新 ref 的數值
- ref 與 state 有何不同
- 如何安全地使用 refs
為你的元件添加 ref
你可以透過從 React 匯入useRefHook 來為元件添加 ref:
在你的元件內部,呼叫useRef Hook 並將你想要引用的初始數值作為唯一參數傳入。例如,這是一個數值為0的 ref:
useRef會回傳一個像這樣的物件:

你可以透過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會導致不可靠的程式碼。如果你需要這樣做,請改用狀態。
何時使用 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 來實現的。然而,即使點擊了「復原」,「已發送!」訊息仍然會出現。找出它無效的原因,並修復它。
