響應事件
React 允許您為 JSX 添加事件處理器。事件處理器是您自己的函數,會在點擊、懸停、聚焦表單輸入等互動時觸發。
您將學習
- 編寫事件處理器的不同方式
- 如何從父元件傳遞事件處理邏輯
- 事件如何傳播以及如何停止它們
添加事件處理器
要添加事件處理器,您需要先定義一個函數,然後將其作為屬性傳遞給相應的 JSX 標籤。例如,這是一個目前還不做任何事情的按鈕:
您可以按照以下三個步驟,讓它在用戶點擊時顯示訊息:
- 在您的
Button元件內部宣告一個名為handleClick的函數。 - 在該函數內部實作邏輯(使用
alert來顯示訊息)。 - 將
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:
PlayButton將handlePlayClick作為onClickprop 傳遞給內部的Button。UploadButton將() => alert('Uploading!')作為onClickprop 傳遞給內部的Button。
最後,你的Button 元件接受一個名為 onClick的 prop。它將該 prop 直接傳遞給內建的瀏覽器<button>,使用onClick={onClick}。這告訴 React 在點擊時呼叫傳入的函式。
如果你使用設計系統,像按鈕這樣的元件通常包含樣式但不指定行為。相反,像 PlayButton 和 UploadButton這樣的元件會向下傳遞事件處理函式。
命名事件處理函式 props
像 <button> 和 <div>這樣的內建元件只支援瀏覽器事件名稱,例如onClick。然而,當你建立自己的元件時,可以隨意命名它們的事件處理函式 props。
按照慣例,事件處理函式 props 應以on開頭,後接一個大寫字母。
例如,Button 元件的 onClickprop 本可以命名為onSmash:
在這個例子中,<button onClick={onSmash}> 顯示瀏覽器的 <button>(小寫)仍然需要一個名為onClick 的 prop,但你的自訂 Button元件接收的 prop 名稱由你決定!
當你的元件支援多種互動時,你可能會根據應用程式的特定概念來命名事件處理函式 props。例如,這個Toolbar 元件接收 onPlayMovie 和 onUploadImage事件處理函式:
請注意App元件並不需要知道ToolbarToolbar會對onPlayMovie或onUploadImage做些什麼。這是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元件:
當您點擊一個按鈕時:
- React 呼叫傳遞給
onClick的<button>處理函式。 - 該處理函式定義於
Button中,執行以下操作:- 呼叫
e.stopPropagation(),阻止事件進一步冒泡。 - 呼叫
onClick函式,該函式是從Toolbar元件傳遞下來的 prop。
- 呼叫
- 該函式定義於
Toolbar元件中,顯示按鈕自身的警示訊息。 - 由於傳播已被阻止,父層
<div>的onClick處理函式 不會執行。
由於 e.stopPropagation()的作用,現在點擊按鈕只會顯示一個警示訊息(來自<button>),而不是兩個(分別來自<button> 和父層工具列的 <div>)。點擊按鈕與點擊其周圍的工具列是不同的操作,因此停止事件傳播對於這個使用者介面來說是合理的。
將處理函數作為傳播的替代方案傳遞
請注意,這個點擊處理函數執行一行程式碼然後呼叫父元件傳遞的onClick 屬性:
您也可以在呼叫父元件的 onClick事件處理函數之前,在此處理函數中添加更多程式碼。這種模式提供了一種傳播的替代方案。它讓子元件處理事件,同時也讓父元件指定一些額外的行為。與傳播不同,它不是自動的。但這種模式的好處是,您可以清楚地追蹤因某個事件而執行的整個程式碼鏈。
如果您依賴傳播,但很難追蹤哪些處理函數執行以及原因,請嘗試改用這種方法。
阻止預設行為
某些瀏覽器事件具有與之關聯的預設行為。例如,<form> 提交事件(當其內部的按鈕被點擊時發生)預設會重新載入整個頁面:
您可以在事件物件上呼叫e.preventDefault() 來阻止這種情況發生:
請勿混淆 e.stopPropagation() 和 e.preventDefault()。它們都很有用,但彼此無關:
- e.stopPropagation()會阻止附加在上層標籤的事件處理器被觸發。
- e.preventDefault()會阻止少數具有預設行為的事件觸發瀏覽器的預設行為。
事件處理器可以有副作用嗎?
當然可以!事件處理器是產生副作用的最佳場所。
與渲染函數不同,事件處理器不需要是純粹的,因此它是改變某些事物的絕佳場所——例如,根據輸入改變輸入框的值,或是根據按鈕點擊改變清單。然而,為了改變某些資訊,首先你需要某種方式來儲存它。在 React 中,這是透過使用狀態(一個元件的記憶體)來完成的。你將在下一頁學到所有相關內容。
總結
- 你可以透過將函數作為屬性傳遞給像
<button>這樣的元素來處理事件。 - 事件處理器必須被傳遞,而不是被呼叫!
onClick={handleClick},而不是onClick={handleClick()}。 - 你可以單獨定義事件處理器函數,或是內聯定義。
- 事件處理器定義在元件內部,因此它們可以存取屬性。
- 你可以在父元件中宣告事件處理器,並將其作為屬性傳遞給子元件。
- 你可以使用應用程式特定的名稱來定義你自己的事件處理器屬性。
- 事件會向上傳播。在第一個參數上呼叫
e.stopPropagation()可以阻止這種行為。 - 事件可能帶有瀏覽器不想要的預設行為。呼叫
e.preventDefault()可以阻止這種行為。 - 從子元件的事件處理器中明確呼叫父元件傳來的事件處理器屬性,是替代事件傳播的一種好方法。
嘗試一些挑戰
Challenge 1 of 2:修復事件處理器 #
點擊此按鈕應該能將頁面背景在白色與黑色之間切換。然而,當你點擊它時,沒有任何反應。請修復這個問題。(不用擔心 handleClick 內部的邏輯——那部分是正常的。)
