React 初心者大補帖(3)

用自動販賣機範例學會 useImperativeHandle

在 React 裡,父元件通常 無法直接控制子元件的內部狀態或行為。但有時候,我們希望 父元件可以透過某些方法操作子元件,例如:重置狀態、讀取資料或觸發某些事件——這時候,useImperativeHandle 就能派上用場。

這篇文章將透過一個「自動販賣機」的範例,一步步了解:

  • 什麼是 useImperativeHandle
  • 它為什麼需要搭配 forwardRef
  • 如何在實務中運用它來建立一個「可由父元件遙控的子元件」

💡 什麼是 useImperativeHandle

useImperativeHandle 是一個 React Hook,讓你可以 自訂一個 ref 對應的物件內容。這樣父元件在透過 ref 取得子元件時,不會拿到整個 DOM 或元件實例,而是取得你「想要暴露出去的功能」。

它通常要和 forwardRef 一起使用,才能讓函式型元件支援 ref。

🧪 範例介紹:一台自動販賣機

假設我們有一個 VendingMachine 元件,他有自己的內部狀態,包含已選擇的商品、投入的金額等等。但通常來說,我們還會需要一些其他的功能,例如客人發現他選錯飲料了,想要取消購買;或是販賣機異常了,工程師來維修的時候想要重置整個飲料機的狀態,這兩個行為都會牽涉到 VendingMachine 的內部狀態,但因為 這些操作是在父元件中觸發的行為,子元件自己無法預測,也不應該自行處理。

這樣做有幾個好處:

  • 保持封裝性:子元件仍然控制自己的 state,外部只能透過「指定的介面」來操作它。
  • 提供明確 API:讓元件的使用者知道有哪些功能可以用,不會亂操作。
  • 應付複雜交互邏輯:像是「取消交易」、「重置系統」這類跨元件操作就能被妥善處理。

因此,在我們的自動販賣機範例中,我們選擇將以下三個功能透過 useImperativeHandle 提供給外部元件使用:

  • cancelSelection: 客人取消交易
  • resetMachine: 工程師重置機器
  • getCurrentStatus: 查詢目前販賣機的狀態

下一步就讓我們來看要怎麼使用 useImperativeHandle 吧。

👷 建立可控制的子元件:VendingMachine

第一步:使用 forwardRef 包裝元件

1
2
3
4
5
const VendingMachine = forwardRef<VendingMachineRef, VendingMachineProps>(
    () => {
    // component
    }
);

第二步:定義要暴露給外部的功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
useImperativeHandle(ref, () => ({
  cancelSelection: () => {
    // 清除狀態並通知取消
  },
  resetMachine: () => {
    // 將狀態重置為初始狀態
  },
  getCurrentStatus: () => {
    // 回傳目前的狀態描述字串
  }
}), [selectedItem, insertedMoney, status, onCancel]);

👨‍🔧 父元件:透過 ref 呼叫子元件方法

第三步:使用 useRef 建立 ref

1
const vendingMachineRef = useRef<VendingMachineRef>(null);

第四步:在父元件中呼叫子元件方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const customerCancel = () => {
  vendingMachineRef.current?.cancelSelection();
};

const technicianReset = () => {
  vendingMachineRef.current?.resetMachine();
};

const checkStatus = () => {
  const status = vendingMachineRef.current?.getCurrentStatus();
  setMachineStatus(status ?? "無法獲取狀態");
};

useImperativeHandle 的應用情境

這種做法特別適合用在:

  • 表單控制:例如讓父元件觸發表單驗證、重置欄位
  • 外部控制元件:像 modal、canvas、圖表、音樂播放器等需要控制的 UI
  • 設計可複用元件 API:把內部邏輯包在元件中,只暴露必要的 API 給使用者

不過要注意的是,useImperativeHandle 不會讓你繞過 React 的「單向資料流」原則,因為你只能操作子元件「允許」的部分。

若父元件可以直接控制全部狀態,那就不需要這個 hook,只有在子元件內部邏輯複雜、但又需要提供部分控制給外部時才用得到。

📎 結語

useImperativeHandle 在實務中雖然使用頻率不高,但在處理一些進階的互動場景(例如這個自動販賣機)時,可以幫你建立乾淨又可控制的元件 API,讓元件設計更彈性、更模組化。

希望你喜歡今天的文章,我們下次再見 👋🏻

Built with Hugo
Theme Stack designed by Jimmy