使用 Refs 操作 DOM
React 會自動更新DOM以匹配你的渲染輸出,因此你的元件通常不需要操作它。然而,有時你可能需要存取由 React 管理的 DOM 元素——例如,聚焦一個節點、滾動到它,或測量其大小和位置。React 中沒有內建的方法來做這些事情,因此你需要一個指向該 DOM 節點的ref。
您將學習
- 如何使用
ref屬性存取由 React 管理的 DOM 節點 - JSX 的
ref屬性與useRefHook 的關係 - 如何存取另一個元件的 DOM 節點
- 在哪些情況下修改由 React 管理的 DOM 是安全的
取得節點的 ref
要存取由 React 管理的 DOM 節點,首先匯入useRefHook:
然後,在你的元件內部用它來宣告一個 ref:
最後,將你的 ref 作為ref屬性傳遞給你想取得其 DOM 節點的 JSX 標籤:
useRef Hook 返回一個具有單一屬性 current的物件。初始時,myRef.current 將是 null。當 React 為這個 <div>建立一個 DOM 節點時,React 會將對該節點的引用放入myRef.current。然後你可以從你的 事件處理器存取這個 DOM 節點,並使用其上定義的內建瀏覽器 API。
範例:聚焦文字輸入框
在此範例中,點擊按鈕將使輸入框獲得焦點:
實作步驟如下:
- 使用
useRefHook 宣告inputRef。 - 將其作為
<input ref={inputRef}>傳遞。這告訴 React將此<input>的 DOM 節點放入inputRef.current中。 - 在
handleClick函式中,從inputRef.current讀取輸入框的 DOM 節點,並使用focus()方法對其呼叫inputRef.current.focus()。 - 透過
onClick將handleClick事件處理器傳遞給<button>。
雖然 DOM 操作是 ref 最常見的用例,但useRefHook 也可用於儲存 React 之外的其他東西,例如計時器 ID。與狀態類似,ref 在渲染之間會保持不變。ref 就像是狀態變數,但設定它們時不會觸發重新渲染。請在使用 Ref 引用值 中閱讀更多關於 ref 的資訊。
範例:捲動至元素
一個元件中可以擁有多個 ref。在此範例中,有一個包含三張圖片的輪播。每個按鈕透過呼叫瀏覽器 scrollIntoView()方法,將對應的 DOM 節點置中:
存取另一個元件的 DOM 節點
陷阱
Ref 是一種逃生艙。手動操作另一個元件的 DOM 節點可能會讓你的程式碼變得脆弱。
你可以將 ref 從父元件傳遞給子元件就像任何其他屬性一樣。
在上面的範例中,ref 是在父元件MyForm中建立的,並傳遞給子元件MyInput。MyInput然後將 ref 傳遞給<input>。因為<input>是一個內建元件,React 會將 ref 的.current屬性設定為<input>DOM 元素。
在MyForm中建立的inputRef現在指向MyInput返回的<input>DOM 元素。在MyForm中建立的點擊處理函式可以存取inputRef並呼叫focus()來將焦點設定在<input>上。
React 何時附加 ref
在 React 中,每次更新都分為兩個階段:
- 在渲染期間,React 呼叫你的元件來計算螢幕上應該顯示什麼。
- 在提交期間,React 將變更應用到 DOM。
一般來說,你不應該在渲染期間存取 ref。這對於持有 DOM 節點的 ref 也是如此。在第一次渲染期間,DOM 節點尚未建立,因此ref.current會是null。而在更新渲染期間,DOM 節點尚未更新。所以現在讀取它們還為時過早。
React 在提交期間設定ref.current。在更新 DOM 之前,React 會將受影響的ref.current值設定為null。在更新 DOM 之後,React 會立即將它們設定為對應的 DOM 節點。
通常,你會從事件處理函式中存取 ref。如果你想對 ref 進行某些操作,但沒有特定的事件來執行,你可能需要一個 Effect。我們將在接下來的頁面中討論 Effect。
使用 ref 操作 DOM 的最佳實踐
Ref 是一種逃生艙。你應該只在必須「跳出 React」時才使用它們。常見的例子包括管理焦點、滾動位置,或是呼叫 React 未公開的瀏覽器 API。
如果你堅持使用非破壞性的操作,例如聚焦和滾動,應該不會遇到任何問題。然而,如果你嘗試手動修改DOM,可能會冒著與 React 正在進行的變更發生衝突的風險。
為了說明這個問題,這個範例包含了一條歡迎訊息和兩個按鈕。第一個按鈕使用條件式渲染和狀態來切換它的顯示與否,這是在 React 中通常的做法。第二個按鈕使用remove() DOM API,在 React 的控制之外強制將其從 DOM 中移除。
試著按幾次「使用 setState 切換」。訊息應該會消失並再次出現。然後按下「從 DOM 中移除」。這將強制移除它。最後,按下「使用 setState 切換」:
在你手動移除 DOM 元素後,嘗試使用setState再次顯示它將會導致崩潰。這是因為你已經改變了 DOM,而 React 不知道如何繼續正確地管理它。
避免修改由 React 管理的 DOM 節點。修改、新增子節點到或從 React 管理的元素中移除子節點,可能會導致視覺結果不一致或像上面那樣的崩潰。
然而,這並不意味著你完全不能這麼做。這需要謹慎。你可以安全地修改 DOM 中 React沒有理由更新的部分。例如,如果某個<div>在 JSX 中總是空的,React 就沒有理由去觸碰它的子節點列表。因此,在那裡手動新增或移除元素是安全的。
總結
- Ref 是一個通用概念,但最常見的是用它來持有 DOM 元素。
- 你可以透過傳遞
myRef.current中。<div ref={myRef}>來指示 React 將一個 DOM 節點放入 - 通常,你會將 ref 用於非破壞性的操作,例如聚焦、滾動或測量 DOM 元素。
- 元件預設不會暴露其 DOM 節點。你可以透過使用
ref屬性來選擇暴露一個 DOM 節點。 - 避免修改由 React 管理的 DOM 節點。
- 如果你確實要修改由 React 管理的 DOM 節點,請修改 React 沒有理由更新的部分。
Try out some challenges
Challenge 1 of 4:Play and pause the video #
In this example, the button toggles a state variable to switch between a playing and a paused state. However, in order to actually play or pause the video, toggling state is not enough. You also need to call play() and pause() on the DOM element for the <video>. Add a ref to it, and make the button work.
For an extra challenge, keep the “Play” button in sync with whether the video is playing even if the user right-clicks the video and plays it using the built-in browser media controls. You might want to listen to onPlay and onPause on the video to do that.
