React + TypeScript:事件型別(React.MouseEvent / React.ChangeEvent)
簡介
在使用 React 搭配 TypeScript 時,最常碰到的挑戰之一,就是正確地為 DOM 事件(點擊、輸入、變更…)指定型別。若型別寫錯,編譯器會直接噴錯,甚至會在執行時出現不可預期的行為。
透過 React.MouseEvent、React.ChangeEvent 等內建的 事件型別,我們不僅能得到完整的 IntelliSense 提示,還能在開發階段即捕捉到錯誤,提升程式碼的可讀性與可靠性。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,一步步帶你掌握 React + TypeScript 中的事件型別,讓你在專案中得心應手。
核心概念
1. 為什麼要使用 React 事件型別?
- 型別安全:
event物件的屬性會根據事件類型自動推斷,例如MouseEvent會有clientX、button等,而ChangeEvent則會提供target.value。 - IDE 補完:使用正確的型別,VS Code 會顯示完整的屬性清單,減少查文件的時間。
- 可維護性:未來若要更換 HTML 元素或改寫事件處理函式,型別會提醒你哪些屬性已不再適用。
2. 兩大常用事件型別
| 型別 | 常見使用情境 | 主要屬性 |
|---|---|---|
React.MouseEvent<T> |
按鈕、連結、圖片的點擊、滑鼠移入/移出 | clientX、clientY、button、currentTarget |
React.ChangeEvent<T> |
<input>、<select>、<textarea> 的值變更 |
target.value、target.checked、currentTarget |
註:
T為事件目標的型別(通常是HTMLButtonElement、HTMLInputElement等),若不特別指定,預設為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;
技巧:使用
value與onChange形成 受控元件,型別會自動保證e.target.value為string。
範例 3:勾選框的 checked 與 value 同時使用
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,同理 KeyboardEvent、FocusEvent 等都有對應型別 |
最佳實踐小結
- 永遠指定目標型別:
React.MouseEvent<HTMLDivElement>、React.ChangeEvent<HTMLInputElement>。 - 利用泛型抽象重複邏輯:如上範例 4 的
ChangeHandler。 - 保持事件處理函式的單一職責:只負責取得資料或觸發 state,避免在同一函式內同時處理 UI 更新與大量邏輯。
- 使用
as const或字面量型別:在select、radio等需要固定選項時,可讓 TypeScript 推斷出更精確的字串聯合型別。 - 加上註解說明:尤其是自訂組件的事件屬性,讓使用者清楚知道回傳的事件型別與可用屬性。
實際應用場景
1. 表單驗證與即時回饋
在大型企業內部系統中,常需要 即時驗證(如 email 格式、密碼強度)並即時顯示錯誤訊息。透過 React.ChangeEvent<HTMLInputElement>,開發者可以在 onChange 內取得使用者輸入值,配合 useState 與 useEffect 完成即時驗證,且型別保證不會因為 target 為 HTMLSelectElement 而產生錯誤。
2. 圖形介面與拖拉功能
React.MouseEvent 在 拖拉排序(drag‑and‑drop)、圖表點擊 等 UI 中極為重要。開發者可以透過 clientX/Y、pageX/Y 取得滑鼠座標,再結合 useRef 保存起始位置,完成自訂拖拉邏輯。型別的存在讓座標屬性在編譯時即被正確辨識,減少除錯時間。
3. 自訂 UI 元件庫
若你正在開發 Design System,每個元件(Button、Input、Select)都需要接受 onClick、onChange 等回呼。使用 React.MouseEvent、React.ChangeEvent 作為介面(interface)的一部分,能確保使用者在呼叫元件時得到正確的型別提示,提升元件庫的可用性與維護性。
4. 手機與桌面雙平台的事件處理
在 React Native Web 或 PWA 中,滑鼠事件與觸控事件會共存。透過 React.MouseEvent 可以在桌面端取得滑鼠資訊,同時使用 React.TouchEvent 處理手機端手勢。把事件型別抽象化後,再根據平台切換對應的 handler,讓程式碼保持 DRY(Don't Repeat Yourself)。
總結
- 事件型別是 React + TypeScript 開發的基礎:正確使用
React.MouseEvent、React.ChangeEvent能讓程式碼更安全、IDE 更聰明、錯誤更早被捕捉。 - 明確指定目標型別(
HTMLButtonElement、HTMLInputElement)是避免any或錯誤屬性的首要步驟。 - 泛型與型別保護 能讓你在同一個 handler 中處理多種表單元件,減少冗餘程式碼。
- 常見陷阱(忘記型別、濫用
any、錯誤的checked使用)只要遵守最佳實踐,就能輕易避開。 - 實務場景(表單驗證、拖拉 UI、元件庫、跨平台)都離不開正確的事件型別,掌握它們,你的 React + TypeScript 專案會更加穩健、易於維護。
祝你在日常開發中,能把 事件型別 用得爐火純青,寫出更乾淨、更可靠的程式碼! 🚀