pidan

pidan

写代码是因为爱,写到世界充满爱!
medium
twitter
telegram
follow

React 中 ref、forwardRef 以及 useImperativeHandle 的使用場景

前言#

React 中,refforwardRef 並不是在所有情況下都必須用到。 React 鼓勵使用聲明式的方式構建 UI,通過 propsstate 來驅動視圖更新。 refforwardRef 屬於更偏向命令式的操作,通常在需要進行 DOM 操作或者訪問組件實例時才會被使用。

使用場景#

什麼情況下 必須 (或者說非常需要 / 強烈建議) 使用 refforwardRef:

1. 直接操作 DOM 元素:

這是 ref 最主要也是最經典的應用場景。 在 React 的聲明式世界裡,我們通常避免直接操作 DOM。 但是,有些場景下,必須 或者 直接操作 DOM 會更方便高效

  • 焦點管理 (Focus Management):
    • 聚焦到特定元素: 比如,表單加載後自動聚焦到第一個輸入框,或者模態框打開後聚焦到第一個可交互元素。
    • 失去焦點時執行操作: 例如,在輸入框失去焦點時觸發驗證。
  • 文本選擇 (Text Selection) 和 操作:
    • 程序化地選中輸入框中的文本: 例如,點擊一個按鈕後,自動選中輸入框中的所有文本。
    • 獲取或設置文本框的光標位置。
  • 媒體元素控制 (Media Element Control):
    • 控制 <video><audio> 元素的播放、暫停、音量等: 例如,實現自定義的視頻播放器控制。
    • 監聽媒體元素的事件: 例如,監聽視頻播放結束事件。
  • Canvas 元素操作:
    • 獲取 Canvas 元素的 Context (2D 或 WebGL) 進行繪圖操作。
  • 集成第三方 DOM 操作庫:
    • 當你需要集成一些直接操作 DOM 的第三方庫 (雖然在現代 React 應用中這種情況越來越少見),例如某些舊的 jQuery 插件,或者一些需要直接訪問 DOM 元素的圖表庫。
  • 觸發 DOM 元素的原生方法:
    • 例如,觸發 input 元素的 focus(), blur(), click(), select() 等方法。

示例:聚焦輸入框

import React, { useRef, useEffect } from 'react';

function MyForm() {
  const inputRef = useRef(null);

  useEffect(() => {
    // 組件掛載後,聚焦到 input 元素
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input type="text" ref={inputRef} placeholder="請輸入..." />
      {/* ... 其他表單元素 ... */}
    </div>
  );
}

2. 訪問子組件的 DOM 節點 (需要 forwardRef):

默認情況下,父組件無法直接通過 ref 訪問子組件的 DOM 節點。 如果你想要在父組件中獲取子組件的 DOM 元素 (例如,子組件是一個自定義的 Input 組件,父組件需要獲取其內部的 <input> 元素), 就需要使用 forwardRef 將 ref 傳遞到子組件內部的 DOM 元素上。

為什麼需要 forwardRef?

這是因為 ref 默認只能綁定到組件實例 (對於 class 組件) 或 DOM 元素。 對於函數式組件,默認情況下 ref 會綁定到組件本身,但函數式組件本身沒有實例,所以 ref 通常為 nullforwardRef 允許你將父組件傳遞的 ref 轉發到子組件的某個 DOM 元素或 class 組件實例上。

示例:父組件獲取子組件 Input 的 DOM 元素

import React, { useRef, forwardRef, useImperativeHandle, useEffect } from 'react';

// 子組件 - 自定義 Input 組件
const CustomInput = forwardRef((props, ref) => {
  const inputElementRef = useRef(null);

  // 使用 useImperativeHandle 暴露子組件的方法 (可選,非 forwardRef 必須)
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputElementRef.current.focus();
    },
    getValue: () => {
      return inputElementRef.current.value;
    }
  }));

  return (
    <div>
      <label htmlFor={props.id}>{props.label}: </label>
      <input type="text" id={props.id} ref={inputElementRef} {...props} />
    </div>
  );
});
CustomInput.displayName = 'CustomInput'; // 方便 React DevTools 顯示

// 父組件
function ParentComponent() {
  const customInputRef = useRef(null);

  useEffect(() => {
    // 組件掛載後,聚焦到 CustomInput 組件內部的 input 元素
    customInputRef.current.focus();

    // 獲取 CustomInput 組件內部 input 的值 (通過 useImperativeHandle 暴露的方法)
    // const inputValue = customInputRef.current.getValue();
    // console.log("Input Value:", inputValue);

  }, []);

  return (
    <div>
      <CustomInput label="姓名" id="nameInput" ref={customInputRef} />
      {/* ... 其他內容 ... */}
    </div>
  );
}

3. 在某些特定的組件庫或場景中需要訪問組件實例 (通過 useImperativeHandle 結合 forwardRef):

雖然 React 提倡數據驅動,但有些情況下,你可能需要在父組件中命令式地調用子組件的方法,例如:

  • 控制子組件的內部狀態或行為: 例如,觸發一個彈窗組件的 open()close() 方法,或者調用一個表格組件的 refreshData() 方法。
  • 集成某些第三方 UI 組件庫: 有些組件庫可能會提供命令式 API,需要通過 ref 獲取組件實例來調用其方法。

使用 useImperativeHandle 控制暴露給父組件的 API:

當你需要讓父組件通過 ref 訪問子組件的方法時,通常會結合 forwardRefuseImperativeHandle Hook。 useImperativeHandle 允許你 精確地控制哪些方法和屬性可以通過 ref 暴露給父組件,避免暴露所有內部細節。

什麼時候應該避免使用 refforwardRef:

  • 一切都應該優先考慮聲明式方式 (props 和 state)。 儘量通過 props 傳遞數據和回調函數來控制子組件的行為和狀態。
  • 避免過度使用 ref 進行組件間的通信。 過多的 ref 使用會使組件關係變得複雜和難以維護,破壞 React 的數據流。
  • 不要為了訪問子組件的 state 而使用 ref 應該通過 props 傳遞數據,讓子組件將數據通過回調函數傳遞給父組件 (狀態提升)。
  • 在沒有明確必要性的情況下,不要濫用 forwardRef 如果父組件不需要直接訪問子組件的 DOM 或實例,就不需要使用 forwardRef

總結:

refforwardRef 是 React 中強大的工具,但應該謹慎使用。 它們在以下情況是必要的或非常有用的:

  • 需要直接操作 DOM 元素時 (例如,焦點管理、媒體控制等)。
  • 需要在父組件中訪問子組件的 DOM 節點時 (使用 forwardRef)。
  • 在某些特定場景下,需要命令式地調用子組件的方法時 (使用 forwardRefuseImperativeHandle)。

記住,優先考慮聲明式的方式解決問題。 只有在聲明式方式難以實現或者命令式操作更加高效簡潔的情況下,才考慮使用 refforwardRef 避免過度使用它們,保持代碼的清晰和可維護性。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。