v19.2Latest

響應事件

React 允許您為 JSX 添加事件處理器。事件處理器是您自己的函數,會在點擊、懸停、聚焦表單輸入等互動時觸發。

您將學習
  • 編寫事件處理器的不同方式
  • 如何從父元件傳遞事件處理邏輯
  • 事件如何傳播以及如何停止它們

添加事件處理器

要添加事件處理器,您需要先定義一個函數,然後將其作為屬性傳遞給相應的 JSX 標籤。例如,這是一個目前還不做任何事情的按鈕:

您可以按照以下三個步驟,讓它在用戶點擊時顯示訊息:

  1. 在您的 Button元件內部宣告一個名為handleClick的函數。
  2. 在該函數內部實作邏輯(使用 alert來顯示訊息)。
  3. onClick={handleClick}添加到<button>JSX 中。

您定義了 handleClick函數,然後將其作為屬性傳遞<button>handleClick是一個事件處理器。事件處理器函數:

  • 通常定義 你的元件內部。
  • 名稱以 handle開頭,後面接著事件名稱。

按照慣例,通常將事件處理器命名為 handle後面加上事件名稱。你經常會看到onClick={handleClick}onMouseEnter={handleMouseEnter} 等等。

或者,你也可以直接在 JSX 中定義一個內聯的事件處理器:

或者,更簡潔地使用箭頭函數:

所有這些風格都是等效的。內聯事件處理器對於簡短的函數來說很方便。

陷阱

傳遞給事件處理器的函式必須是傳遞,而不是呼叫。例如:

傳遞函式(正確)呼叫函式(錯誤)
<button onClick={handleClick}><button onClick={handleClick()}>

差異很細微。在第一個例子中,handleClick 函式被作為 onClick事件處理器傳遞。這告訴 React 記住它,並只在用戶點擊按鈕時呼叫你的函式。

在第二個例子中,handleClick() 末尾的 () 會在 立即觸發該函式,無需任何點擊。這是因為渲染 過程中 JSX { 和 }內的 JavaScript 會立即執行。

當你編寫內聯程式碼時,同樣的陷阱會以不同的方式呈現:

傳遞函式(正確)呼叫函式(錯誤)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

像這樣傳遞內聯程式碼不會在點擊時觸發——它會在每次元件渲染時觸發:

如果你想內聯定義事件處理器,請將其包裹在一個匿名函式中,如下所示:

這樣做不是每次渲染都執行內部的程式碼,而是建立一個稍後被呼叫的函式。

在這兩種情況下,你想要傳遞的都是一個函式:

  • <button onClick={handleClick}> 傳遞 handleClick函式。
  • <button onClick={() => alert('...')}> 傳遞 () => alert('...')函式。

閱讀更多關於箭頭函式的資訊。

在事件處理函式中讀取 props

由於事件處理函式是在元件內部宣告的,因此它們可以存取元件的 props。以下是一個按鈕,當點擊時,會顯示一個包含其messageprop 的警示:

這讓這兩個按鈕可以顯示不同的訊息。試著更改傳遞給它們的訊息。

將事件處理函式作為 props 傳遞

通常你會希望父元件指定子元件的事件處理函式。以按鈕為例:根據你使用 Button元件的位置,你可能想要執行不同的函式——也許一個播放電影,另一個上傳圖片。

為此,將元件從其父元件接收到的 prop 作為事件處理函式傳遞,如下所示:

這裡,Toolbar元件渲染了一個PlayButton和一個UploadButton

  • PlayButtonhandlePlayClick 作為 onClickprop 傳遞給內部的Button
  • UploadButton() => alert('Uploading!') 作為 onClickprop 傳遞給內部的Button

最後,你的Button 元件接受一個名為 onClick的 prop。它將該 prop 直接傳遞給內建的瀏覽器<button>,使用onClick={onClick}。這告訴 React 在點擊時呼叫傳入的函式。

如果你使用設計系統,像按鈕這樣的元件通常包含樣式但不指定行為。相反,像 PlayButtonUploadButton這樣的元件會向下傳遞事件處理函式。

命名事件處理函式 props

<button><div>這樣的內建元件只支援瀏覽器事件名稱,例如onClick。然而,當你建立自己的元件時,可以隨意命名它們的事件處理函式 props。

按照慣例,事件處理函式 props 應以on開頭,後接一個大寫字母。

例如,Button 元件的 onClickprop 本可以命名為onSmash

在這個例子中,<button onClick={onSmash}> 顯示瀏覽器的 <button>(小寫)仍然需要一個名為onClick 的 prop,但你的自訂 Button元件接收的 prop 名稱由你決定!

當你的元件支援多種互動時,你可能會根據應用程式的特定概念來命名事件處理函式 props。例如,這個Toolbar 元件接收 onPlayMovieonUploadImage事件處理函式:

請注意App元件並不需要知道ToolbarToolbar會對onPlayMovieonUploadImage做些什麼。這是Toolbar的實作細節。在這裡,Toolbar將它們作為onClick處理函式傳遞給它的Button,但之後也可能透過鍵盤快捷鍵觸發它們。將屬性命名為應用程式特定的互動名稱,例如onPlayMovie,讓您能夠靈活地在未來改變它們的使用方式。

注意

請確保為您的事件處理器使用適當的HTML標籤。例如,要處理點擊事件,請使用<button onClick={handleClick}>,而不是<div onClick={handleClick}>。使用真正的瀏覽器<button>可以啟用內建的瀏覽器行為,例如鍵盤導航。如果您不喜歡按鈕的預設瀏覽器樣式,並希望讓它看起來更像連結或其他UI元素,可以使用CSS來實現。了解更多關於撰寫無障礙標記的資訊。

事件傳遞

事件處理器也會捕捉元件可能擁有的任何子元件所產生的事件。我們說事件會「冒泡」或「傳遞」到樹狀結構的上層:它從事件發生的地方開始,然後向上傳遞。

這個<div>包含兩個按鈕。<div>每個按鈕都有自己的onClick處理器。當您點擊一個按鈕時,您認為哪些處理器會被觸發?

如果您點擊任一按鈕,其onClick會先執行,接著是父層<div>onClick。因此會出現兩條訊息。如果您點擊工具列本身,則只有父層<div>onClick會執行。

陷阱

在React中,除了onScrollonScroll之外的所有事件都會傳遞,而僅作用於您附加它的JSX標籤。

停止傳遞

事件處理器接收一個事件物件作為其唯一的參數。按照慣例,它通常被稱為e,代表「事件」。您可以使用此物件來讀取事件的相關資訊。

該事件物件也允許您停止傳遞。如果您想防止事件到達父元件,您需要像這個e.stopPropagation()一樣呼叫Button元件:

當您點擊一個按鈕時:

  1. React 呼叫傳遞給onClick<button>處理函式。
  2. 該處理函式定義於Button中,執行以下操作:
    • 呼叫e.stopPropagation(),阻止事件進一步冒泡。
    • 呼叫 onClick 函式,該函式是從 Toolbar元件傳遞下來的 prop。
  3. 該函式定義於 Toolbar元件中,顯示按鈕自身的警示訊息。
  4. 由於傳播已被阻止,父層 <div>onClick 處理函式 不會執行。

由於 e.stopPropagation()的作用,現在點擊按鈕只會顯示一個警示訊息(來自<button>),而不是兩個(分別來自<button> 和父層工具列的 <div>)。點擊按鈕與點擊其周圍的工具列是不同的操作,因此停止事件傳播對於這個使用者介面來說是合理的。

Deep Dive
捕獲階段事件

將處理函數作為傳播的替代方案傳遞

請注意,這個點擊處理函數執行一行程式碼然後呼叫父元件傳遞的onClick 屬性:

您也可以在呼叫父元件的 onClick事件處理函數之前,在此處理函數中添加更多程式碼。這種模式提供了一種傳播的替代方案。它讓子元件處理事件,同時也讓父元件指定一些額外的行為。與傳播不同,它不是自動的。但這種模式的好處是,您可以清楚地追蹤因某個事件而執行的整個程式碼鏈。

如果您依賴傳播,但很難追蹤哪些處理函數執行以及原因,請嘗試改用這種方法。

阻止預設行為

某些瀏覽器事件具有與之關聯的預設行為。例如,<form> 提交事件(當其內部的按鈕被點擊時發生)預設會重新載入整個頁面:

您可以在事件物件上呼叫e.preventDefault() 來阻止這種情況發生:

請勿混淆 e.stopPropagation()e.preventDefault()。它們都很有用,但彼此無關:

事件處理器可以有副作用嗎?

當然可以!事件處理器是產生副作用的最佳場所。

與渲染函數不同,事件處理器不需要是純粹的,因此它是改變某些事物的絕佳場所——例如,根據輸入改變輸入框的值,或是根據按鈕點擊改變清單。然而,為了改變某些資訊,首先你需要某種方式來儲存它。在 React 中,這是透過使用狀態(一個元件的記憶體)來完成的。你將在下一頁學到所有相關內容。

總結

  • 你可以透過將函數作為屬性傳遞給像<button>這樣的元素來處理事件。
  • 事件處理器必須被傳遞,而不是被呼叫!onClick={handleClick},而不是onClick={handleClick()}
  • 你可以單獨定義事件處理器函數,或是內聯定義。
  • 事件處理器定義在元件內部,因此它們可以存取屬性。
  • 你可以在父元件中宣告事件處理器,並將其作為屬性傳遞給子元件。
  • 你可以使用應用程式特定的名稱來定義你自己的事件處理器屬性。
  • 事件會向上傳播。在第一個參數上呼叫e.stopPropagation()可以阻止這種行為。
  • 事件可能帶有瀏覽器不想要的預設行為。呼叫e.preventDefault()可以阻止這種行為。
  • 從子元件的事件處理器中明確呼叫父元件傳來的事件處理器屬性,是替代事件傳播的一種好方法。

嘗試一些挑戰

Challenge 1 of 2:修復事件處理器 #

點擊此按鈕應該能將頁面背景在白色與黑色之間切換。然而,當你點擊它時,沒有任何反應。請修復這個問題。(不用擔心 handleClick 內部的邏輯——那部分是正常的。)