React 事件處理 (Event Handling)

在 React 中處理事件(events),跟處理 DOM 事件很類似,但有些微的差異:

  • HTML DOM 的 event handler(事件處理函式)屬性名稱都是小寫的,但 React 是 camelCase
  • HTML DOM 的 event handler 屬性的值是一個字串,但在 JSX 中是一個函數
  • 在 React 的事件處理函式中,你不能像在 DOM 可以 return false 代表停掉瀏覽器預設行為,你需要明確的呼叫 preventDefault()

像是在 HTML 中:

<button onclick="handleClick()">Click me</button>

而在 React 中:

<button onClick={handleClick}>Click me</button>

注意 React 的 onClick 是 camelCase,而且值是 {handleClick} 函式本身,不是字串。

基本事件處理

在 Function Component 中定義事件處理函式:

function Button() {
  function handleClick() {
    alert('你點擊了按鈕!')
  }

  return <button onClick={handleClick}>點我</button>
}

你也可以直接在 JSX 中使用 inline arrow function:

function Button() {
  return <button onClick={() => alert('你點擊了按鈕!')}>點我</button>
}
簡單的邏輯可以用 inline function,但如果邏輯複雜,建議抽出成獨立的函式,讓程式碼更易讀。

Event Object

事件處理函式會接收一個 event object 作為參數,這是 React 的 SyntheticEvent:

function Form() {
  function handleSubmit(event) {
    // 阻止表單預設的提交行為
    event.preventDefault()
    console.log('表單已提交')
  }

  function handleChange(event) {
    // 取得輸入欄位的值
    console.log('輸入的值:', event.target.value)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
      <button type="submit">送出</button>
    </form>
  )
}

常用的 event object 屬性:

  • event.target:觸發事件的 DOM 元素
  • event.target.value:表單元素的值
  • event.preventDefault():阻止預設行為
  • event.stopPropagation():阻止事件冒泡

合成事件 (SyntheticEvent)

React 有一層用來處理事件的機制,稱作 SyntheticEvent(合成事件)。SyntheticEvent 幫你處理好跨瀏覽器的問題(cross-browser compatibility),確保 React 的事件模型和 W3C 標準保持一致!

SyntheticEvent 包裝了原生的 DOM 事件,提供統一的介面。如果你需要存取原生的事件物件,可以透過 event.nativeEvent

傳遞參數給事件處理函式

如果你需要傳遞額外的參數給事件處理函式,可以使用 arrow function:

function ItemList() {
  const items = ['蘋果', '香蕉', '橘子']

  function handleDelete(itemName) {
    console.log('刪除:', itemName)
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={item}>
          {item}
          <button onClick={() => handleDelete(item)}>刪除</button>
        </li>
      ))}
    </ul>
  )
}

如果同時需要 event object 和其他參數:

function handleClick(id, event) {
  event.preventDefault()
  console.log('ID:', id)
}

// 使用時
;<button onClick={(e) => handleClick(123, e)}>Click</button>

常用的事件類型

滑鼠事件

<div
  onClick={() => console.log('click')}
  onDoubleClick={() => console.log('double click')}
  onMouseEnter={() => console.log('mouse enter')}
  onMouseLeave={() => console.log('mouse leave')}
  onMouseMove={(e) => console.log('position:', e.clientX, e.clientY)}
>
  滑鼠事件區域
</div>

鍵盤事件

<input
  onKeyDown={(e) => console.log('key down:', e.key)}
  onKeyUp={(e) => console.log('key up:', e.key)}
  onKeyPress={(e) => console.log('key press:', e.key)}
/>

常見用法 - 按 Enter 送出:

function SearchInput() {
  const [query, setQuery] = useState('')

  function handleKeyDown(event) {
    if (event.key === 'Enter') {
      console.log('搜尋:', query)
    }
  }

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="按 Enter 搜尋"
    />
  )
}

表單事件

<input
  onChange={(e) => console.log('value changed:', e.target.value)}
  onFocus={() => console.log('focused')}
  onBlur={() => console.log('blurred')}
/>

<form onSubmit={(e) => {
  e.preventDefault();
  console.log('form submitted');
}}>
  {/* form content */}
</form>

焦點事件

<input onFocus={() => console.log('獲得焦點')} onBlur={() => console.log('失去焦點')} />

事件冒泡與捕獲

React 的事件會像 DOM 事件一樣冒泡(bubble)。你可以使用 event.stopPropagation() 來阻止事件冒泡:

function Parent() {
  return (
    <div onClick={() => console.log('Parent clicked')}>
      <button
        onClick={(e) => {
          e.stopPropagation()
          console.log('Button clicked')
        }}
      >
        點我
      </button>
    </div>
  )
}

點擊按鈕時,只會印出 "Button clicked",不會觸發 parent 的 onClick。

捕獲階段的事件

如果你需要在捕獲階段處理事件,可以使用 onClickCapture 等 Capture 結尾的事件:

<div onClickCapture={() => console.log('Capture phase')}>
  <button onClick={() => console.log('Bubble phase')}>Click</button>
</div>

實際範例:開關按鈕

import { useState } from 'react'

function Toggle() {
  const [isOn, setIsOn] = useState(false)

  function handleClick() {
    setIsOn((prev) => !prev)
  }

  return (
    <button
      onClick={handleClick}
      style={{
        backgroundColor: isOn ? '#4CAF50' : '#f44336',
        color: 'white',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer',
      }}
    >
      {isOn ? '開' : '關'}
    </button>
  )
}

實際範例:表單輸入

import { useState } from 'react'

function LoginForm() {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
  })

  function handleChange(event) {
    const { name, value } = event.target
    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }))
  }

  function handleSubmit(event) {
    event.preventDefault()
    console.log('登入資料:', formData)
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="username">帳號:</label>
        <input
          type="text"
          id="username"
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="password">密碼:</label>
        <input
          type="password"
          id="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />
      </div>
      <button type="submit">登入</button>
    </form>
  )
}