> ## Content Index
> Fetch the complete content index at: https://blog.anthea.day/llms.txt
> Use this file to discover other available public pages before exploring further.

# React 初心者大補帖（3）：用自動販賣機範例學會 useImperativeHandle
- URL: https://blog.anthea.day/series-react-beginner-3/
- Published: 2025-06-06T03:53:17.000Z
- Updated: 2026-11-07T19:59:20.000Z
- Author: Anthea / 芫荽
- Tags: #Migrated-1792241843742, #Import 2026-10-17 12:57, Coding, React 初心者大補帖

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

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

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

---

### 💡 什麼是 useImperativeHandle

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

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

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

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

這樣做有幾個好處：

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

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

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

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

---

#### 👷 建立可控制的子元件：VendingMachine

**第一步：使用 forwardRef 包裝元件**

```javascript
const VendingMachine = forwardRef<VendingMachineRef, VendingMachineProps>( 
    () => { 
    // component 
    } 
);
```

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

```javascript
useImperativeHandle(ref, () => ({ 
  cancelSelection: () => { 
    // 清除狀態並通知取消 
  }, 
  resetMachine: () => { 
    // 將狀態重置為初始狀態 
  }, 
  getCurrentStatus: () => { 
    // 回傳目前的狀態描述字串 
  } 
}), [selectedItem, insertedMoney, status, onCancel]);
```

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

**第三步：使用 `useRef` 建立 ref**

```csharp
const vendingMachineRef = useRef<VendingMachineRef>(null);
```

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

```javascript
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，讓元件設計更彈性、更模組化。

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