本文 AI 產出,尚未審核

React + TypeScript:事件型別(React.MouseEvent / React.ChangeEvent


簡介

在使用 React 搭配 TypeScript 時,最常碰到的挑戰之一,就是正確地為 DOM 事件(點擊、輸入、變更…)指定型別。若型別寫錯,編譯器會直接噴錯,甚至會在執行時出現不可預期的行為。
透過 React.MouseEventReact.ChangeEvent 等內建的 事件型別,我們不僅能得到完整的 IntelliSense 提示,還能在開發階段即捕捉到錯誤,提升程式碼的可讀性與可靠性。

本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,一步步帶你掌握 React + TypeScript 中的事件型別,讓你在專案中得心應手。


核心概念

1. 為什麼要使用 React 事件型別?

  • 型別安全event 物件的屬性會根據事件類型自動推斷,例如 MouseEvent 會有 clientXbutton 等,而 ChangeEvent 則會提供 target.value
  • IDE 補完:使用正確的型別,VS Code 會顯示完整的屬性清單,減少查文件的時間。
  • 可維護性:未來若要更換 HTML 元素或改寫事件處理函式,型別會提醒你哪些屬性已不再適用。

2. 兩大常用事件型別

型別 常見使用情境 主要屬性
React.MouseEvent<T> 按鈕、連結、圖片的點擊、滑鼠移入/移出 clientXclientYbuttoncurrentTarget
React.ChangeEvent<T> <input><select><textarea> 的值變更 target.valuetarget.checkedcurrentTarget

T 為事件目標的型別(通常是 HTMLButtonElementHTMLInputElement 等),若不特別指定,預設為 Element

3. 事件處理函式的型別寫法

// 基本寫法
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.clientX, e.clientY);
};

// 若使用泛型,可寫成
type MouseHandler<T = Element> = (e: React.MouseEvent<T>) => void;
const handleDivClick: MouseHandler<HTMLDivElement> = (e) => {
  // e 會被推斷為 React.MouseEvent<HTMLDivElement>
  console.log(e.currentTarget.id);
};

4. React.ChangeEvent 的細節

ChangeEvent 會在 表單控制元件<input><select><textarea>)的值變化時觸發。最常見的需求是取得使用者輸入的文字或勾選狀態:

const handleInputChange = (
  e: React.ChangeEvent<HTMLInputElement>
) => {
  // 文字框
  const value = e.target.value;
  // 勾選框
  const checked = e.target.checked;
  console.log(value, checked);
};

小技巧:若你同時處理多種表單元件,可以使用聯合型別 HTMLInputElement | HTMLSelectElement,再利用型別保護 (if) 取得正確屬性。


程式碼範例

以下範例均以 React Function Component + TypeScript 為前提,展示不同情境下的事件型別寫法與實務應用。

範例 1:按鈕點擊取得滑鼠座標

import React from "react";

const ClickPosition: React.FC = () => {
  const handleClick = (
    e: React.MouseEvent<HTMLButtonElement>
  ) => {
    // 取得點擊時的螢幕座標
    const { clientX, clientY } = e;
    alert(`你點擊的位置:X=${clientX}, Y=${clientY}`);
  };

  return (
    <button onClick={handleClick}>
      點我顯示座標
    </button>
  );
};

export default ClickPosition;

重點e.currentTarget<button> 本身,而 e.clientX/Y 為滑鼠座標。


範例 2:文字輸入即時更新狀態

import React, { useState } from "react";

const InputMirror: React.FC = () => {
  const [text, setText] = useState("");

  const handleChange = (
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    setText(e.target.value); // 直接取得 value
  };

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={handleChange}
        placeholder="輸入文字即時顯示"
      />
      <p>你輸入的:{text}</p>
    </div>
  );
};

export default InputMirror;

技巧:使用 valueonChange 形成 受控元件,型別會自動保證 e.target.valuestring


範例 3:勾選框的 checkedvalue 同時使用

import React, { useState } from "react";

interface Option {
  label: string;
  value: string;
}

const CheckboxGroup: React.FC = () => {
  const options: Option[] = [
    { label: "Apple", value: "apple" },
    { label: "Banana", value: "banana" },
    { label: "Cherry", value: "cherry" },
  ];

  const [selected, setSelected] = useState<string[]>([]);

  const handleCheck = (
    e: React.ChangeEvent<HTMLInputElement>
  ) => {
    const { value, checked } = e.target;
    setSelected(prev =>
      checked
        ? [...prev, value]
        : prev.filter(v => v !== value)
    );
  };

  return (
    <div>
      {options.map(opt => (
        <label key={opt.value} style={{ display: "block" }}>
          <input
            type="checkbox"
            value={opt.value}
            checked={selected.includes(opt.value)}
            onChange={handleCheck}
          />
          {opt.label}
        </label>
      ))}
      <p>已選擇:{selected.join(", ")}</p>
    </div>
  );
};

export default CheckboxGroup;

說明e.target.checked 為布林值,e.target.value 為字串。兩者同時使用時,型別不會衝突。


範例 4:使用泛型建立通用的事件處理器

import React, { ChangeEvent } from "react";

type ChangeHandler<T extends HTMLInputElement | HTMLSelectElement> = (
  e: ChangeEvent<T>
) => void;

// 文字框
const TextInput: React.FC = () => {
  const handleChange: ChangeHandler<HTMLInputElement> = e => {
    console.log("文字:", e.target.value);
  };
  return <input type="text" onChange={handleChange} />;
};

// 下拉選單
const SelectBox: React.FC = () => {
  const handleChange: ChangeHandler<HTMLSelectElement> = e => {
    console.log("選項:", e.target.value);
  };
  return (
    <select onChange={handleChange}>
      <option value="A">A</option>
      <option value="B">B</option>
    </select>
  );
};

優點:一次定義 ChangeHandler,即可在不同表單元件間重複使用,減少程式碼重複。


範例 5:自訂組件的事件型別(ForwardRef + MouseEvent)

import React, { forwardRef, MouseEvent } from "react";

type ButtonProps = {
  label: string;
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
};

const FancyButton = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ label, onClick }, ref) => (
    <button ref={ref} onClick={onClick} className="fancy">
      {label}
    </button>
  )
);

export default FancyButton;

說明:即使是自訂組件,也可以直接使用 React.MouseEvent 讓外部呼叫者取得完整的滑鼠事件資訊。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記指定目標型別 e: React.MouseEvent 會預設為 Element,導致 e.currentTarget 沒有特定屬性(如 value 明確寫 React.MouseEvent<HTMLButtonElement> 或使用泛型
SyntheticEvent 當成原生 Event 直接使用 e.nativeEvent 會失去 TypeScript 的型別推斷 盡量使用 React 提供的合成事件屬性;若真的需要原生事件,使用 e.nativeEvent as MouseEvent 進行斷言
onChange 中直接使用 e.target.checked 於非 checkbox 會得到 undefined,且編譯器不會提示錯誤 使用 型別保護if ('checked' in e.target) { ... }
事件處理函式過於通用,導致型別變成 any 例如 const handle = (e) => {...} 會失去所有提示 為每個 handler 明確標註型別,或使用通用型別 `React.ChangeEvent<HTMLInputElement
忘記 preventDefault 的型別 e.preventDefault() 沒有問題,但若想要在 onSubmit 中取得 FormEvent,必須使用 React.FormEvent<HTMLFormElement> 為表單事件使用 React.FormEvent,同理 KeyboardEventFocusEvent 等都有對應型別

最佳實踐小結

  1. 永遠指定目標型別React.MouseEvent<HTMLDivElement>React.ChangeEvent<HTMLInputElement>
  2. 利用泛型抽象重複邏輯:如上範例 4 的 ChangeHandler
  3. 保持事件處理函式的單一職責:只負責取得資料或觸發 state,避免在同一函式內同時處理 UI 更新與大量邏輯。
  4. 使用 as const 或字面量型別:在 selectradio 等需要固定選項時,可讓 TypeScript 推斷出更精確的字串聯合型別。
  5. 加上註解說明:尤其是自訂組件的事件屬性,讓使用者清楚知道回傳的事件型別與可用屬性。

實際應用場景

1. 表單驗證與即時回饋

在大型企業內部系統中,常需要 即時驗證(如 email 格式、密碼強度)並即時顯示錯誤訊息。透過 React.ChangeEvent<HTMLInputElement>,開發者可以在 onChange 內取得使用者輸入值,配合 useStateuseEffect 完成即時驗證,且型別保證不會因為 targetHTMLSelectElement 而產生錯誤。

2. 圖形介面與拖拉功能

React.MouseEvent拖拉排序(drag‑and‑drop)圖表點擊 等 UI 中極為重要。開發者可以透過 clientX/YpageX/Y 取得滑鼠座標,再結合 useRef 保存起始位置,完成自訂拖拉邏輯。型別的存在讓座標屬性在編譯時即被正確辨識,減少除錯時間。

3. 自訂 UI 元件庫

若你正在開發 Design System,每個元件(Button、Input、Select)都需要接受 onClickonChange 等回呼。使用 React.MouseEventReact.ChangeEvent 作為介面(interface)的一部分,能確保使用者在呼叫元件時得到正確的型別提示,提升元件庫的可用性與維護性。

4. 手機與桌面雙平台的事件處理

React Native WebPWA 中,滑鼠事件與觸控事件會共存。透過 React.MouseEvent 可以在桌面端取得滑鼠資訊,同時使用 React.TouchEvent 處理手機端手勢。把事件型別抽象化後,再根據平台切換對應的 handler,讓程式碼保持 DRY(Don't Repeat Yourself)。


總結

  • 事件型別是 React + TypeScript 開發的基礎:正確使用 React.MouseEventReact.ChangeEvent 能讓程式碼更安全、IDE 更聰明、錯誤更早被捕捉。
  • 明確指定目標型別HTMLButtonElementHTMLInputElement)是避免 any 或錯誤屬性的首要步驟。
  • 泛型與型別保護 能讓你在同一個 handler 中處理多種表單元件,減少冗餘程式碼。
  • 常見陷阱(忘記型別、濫用 any、錯誤的 checked 使用)只要遵守最佳實踐,就能輕易避開。
  • 實務場景(表單驗證、拖拉 UI、元件庫、跨平台)都離不開正確的事件型別,掌握它們,你的 React + TypeScript 專案會更加穩健、易於維護。

祝你在日常開發中,能把 事件型別 用得爐火純青,寫出更乾淨、更可靠的程式碼! 🚀