React 允許你在 JSX 中新增事件處理器。事件處理器是你自行定義的函式,會在點擊、滑鼠懸停、表單輸入框獲得焦點等互動時觸發。
你將學到
- 編寫事件處理器的不同方式
- 如何從父元件傳遞事件處理邏輯
- 事件如何傳播以及如何停止它們
新增事件處理器
要新增事件處理器,你首先要定義一個函式,然後將它作為 prop 傳遞給適當的 JSX 標籤。例如,這裡有一個目前還沒有任何功能的按鈕
export default function Button() { return ( <button> I don't do anything </button> ); }
你可以按照以下三個步驟,讓它在使用者點擊時顯示訊息
- 在你的
Button
元件內部宣告一個名為handleClick
的函式。 - 在該函式內實作邏輯(使用
alert
顯示訊息)。 - 將
onClick={handleClick}
新增到<button>
JSX 中。
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
你定義了 handleClick
函式,然後將它作為 prop 傳遞給 <button>
。handleClick
是一個事件處理器。 事件處理器函式
- 通常定義在你的元件內部。
- 名稱以
handle
開頭,後跟事件的名稱。
按照慣例,事件處理器的名稱通常是 handle
後跟事件名稱。你會經常看到 onClick={handleClick}
、onMouseEnter={handleMouseEnter}
等等。
或者,你可以在 JSX 中內聯定義事件處理器
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
或者,更簡潔地使用箭頭函式
<button onClick={() => {
alert('You clicked me!');
}}>
所有這些樣式都是等效的。內聯事件處理器適用於簡短的函式。
在事件處理器中讀取 props
由於事件處理器是在元件內部宣告的,因此它們可以存取元件的 props。這裡有一個按鈕,當點擊它時,會顯示一個包含其 message
prop 的提示訊息。
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Playing!"> Play Movie </AlertButton> <AlertButton message="Uploading!"> Upload Image </AlertButton> </div> ); }
這讓這兩個按鈕顯示不同的訊息。嘗試更改傳遞給它們的訊息。
將事件處理器作為 props 傳遞
通常,您會希望父元件指定子元件的事件處理器。考慮按鈕:根據您使用 Button
元件的位置,您可能想要執行不同的函式 — 也許一個播放電影,另一個上傳圖片。
要做到這一點,請將元件從其父元件接收的 prop 作為事件處理器傳遞,如下所示:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Playing ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Play "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Uploading!')}> Upload Image </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
在這裡,Toolbar
元件渲染一個 PlayButton
和一個 UploadButton
。
PlayButton
將handlePlayClick
作為onClick
prop 傳遞給內部的Button
。UploadButton
將() => alert('正在上傳!')
作為onClick
prop 傳遞給內部的Button
。
最後,您的 Button
元件接受一個名為 onClick
的 prop。它使用 onClick={onClick}
將該 prop 直接傳遞給內建的瀏覽器 <button>
。這告訴 React 在點擊時呼叫傳遞的函式。
如果您使用設計系統,像按鈕這樣的元件通常包含樣式但不指定行為。相反,像 PlayButton
和 UploadButton
這樣的元件會將事件處理器向下傳遞。
命名事件處理器 props
像 <button>
和 <div>
這樣的內建元件僅支援瀏覽器事件名稱,例如 onClick
。但是,當您構建自己的元件時,您可以根據自己的喜好命名事件處理器 props。
按照慣例,事件處理器 props 應以 on
開頭,後跟一個大寫字母。
例如,Button
元件的 onClick
prop 可以稱為 onSmash
。
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Playing!')}> Play Movie </Button> <Button onSmash={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
在此範例中,<button onClick={onSmash}>
顯示瀏覽器 <button>
(小寫)仍然需要一個名為 onClick
的 prop,但是您的自定義 Button
元件接收的 prop 名稱由您決定!
當您的元件支援多種互動時,您可以根據應用程式特定的概念來命名事件處理器 props。例如,這個 Toolbar
元件接收 onPlayMovie
和 onUploadImage
事件處理器。
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Playing!')} onUploadImage={() => alert('Uploading!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Play Movie </Button> <Button onClick={onUploadImage}> Upload Image </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
請注意,App
元件不需要知道 Toolbar
將如何處理 onPlayMovie
或 onUploadImage
。這是 Toolbar
的實作細節。在這裡,Toolbar
將它們作為 onClick
處理器向下傳遞給其 Button
,但它之後也可以透過鍵盤快捷鍵觸發它們。根據應用程式特定的互動(例如 onPlayMovie
)命名 props,讓您日後可以彈性地更改它們的使用方式。
事件傳播
事件處理器也會攔截來自元件任何子元件的事件。我們說一個事件會「向上冒泡」或「傳播」:它從事件發生的位置開始,然後向上傳播到樹狀結構。
這個 <div>
包含兩個按鈕。<div>
*和* 每個按鈕都有自己的 onClick
處理器。您認為當您點擊一個按鈕時,哪些處理器會觸發?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <button onClick={() => alert('Playing!')}> Play Movie </button> <button onClick={() => alert('Uploading!')}> Upload Image </button> </div> ); }
如果您點擊任一按鈕,它的 onClick
會先執行,接著是父層 <div>
的 onClick
。因此會出現兩則訊息。如果您點擊工具列本身,則只有父層 <div>
的 onClick
會執行。
停止事件傳播
事件處理器會接收一個 **事件物件** 作為其唯一參數。按照慣例,它通常被稱為 e
,代表「事件」。您可以使用此物件來讀取有關事件的資訊。
該事件物件也允許您停止事件傳播。如果您要防止事件到達父元件,則需要呼叫 e.stopPropagation()
,就像這個 Button
元件所做的一樣。
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <Button onClick={() => alert('Playing!')}> Play Movie </Button> <Button onClick={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
當您點擊按鈕時
- React 會呼叫傳遞給
<button>
的onClick
處理器。 - 在
Button
中定義的該處理器會執行以下操作:- 呼叫
e.stopPropagation()
,防止事件進一步向上冒泡。 - 呼叫
onClick
函式,它是從Toolbar
元件傳遞的屬性。
- 呼叫
- 在
Toolbar
元件中定義的該函式會顯示按鈕自身的警示訊息。 - 由於事件傳播已停止,父層
<div>
的onClick
處理器*不會* 執行。
由於 e.stopPropagation()
的作用,點擊按鈕現在只會顯示一則警示訊息(來自 <button>
),而不是兩則(來自 <button>
和父工具列 <div>
)。點擊按鈕與點擊周圍的工具列不同,因此停止傳播對於此 UI 來說是有意義的。
深入探討
在極少數情況下,您可能需要捕獲子元素上的所有事件,*即使它們已停止傳播*。例如,也許您想將每次點擊記錄到分析中,無論傳播邏輯如何。您可以透過在事件名稱後面加上 Capture
來做到這一點。
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
每個事件都分三個階段傳播:
- 它向下傳播,呼叫所有
onClickCapture
處理器。 - 它執行被點擊元素的
onClick
處理器。 - 它向上傳播,呼叫所有
onClick
處理器。
捕獲事件適用於路由器或分析等程式碼,但您可能不會在應用程式程式碼中使用它們。
傳遞處理器作為傳播的替代方案
請注意,此點擊處理器如何執行一行程式碼,*然後* 呼叫由父層傳遞的 onClick
屬性。
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
您也可以在呼叫父層 onClick
事件處理器之前,在此處理器中新增更多程式碼。此模式提供了一種傳播的*替代方案*。它允許子元件處理事件,同時也允許父元件指定一些額外行為。與傳播不同,它不是自動的。但這種模式的好處是,您可以清楚地追蹤因某些事件而執行的整個程式碼鏈。
如果您依賴傳播,並且難以追蹤哪些處理器執行以及原因,請改用這種方法。
防止預設行為
某些瀏覽器事件具有與之關聯的預設行為。例如,當點擊 <form>
內部的按鈕時發生的提交事件,預設情況下會重新載入整個頁面。
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
您可以呼叫事件物件上的 e.preventDefault()
來阻止這種情況發生。
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
不要混淆 e.stopPropagation()
和 e.preventDefault()
。它們都很有用,但彼此無關。
e.stopPropagation()
會阻止附加到上方標籤的事件處理器觸發。e.preventDefault()
會防止瀏覽器對少數具有預設行為的事件執行預設行為。
事件處理器可以有副作用嗎?
當然!事件處理器是最適合放置副作用的地方。
與渲染函式不同,事件處理器不需要是 純粹的,所以它是個改變某些東西的好地方——例如,響應輸入而改變輸入值,或者響應按鈕按下而改變列表。然而,為了改變某些資訊,你首先需要某種方式來儲存它。在 React 中,這是透過使用 狀態,一個元件的記憶體 來完成的。你將在下一頁學習所有相關知識。
重點回顧
- 你可以透過將函式作為 prop 傳遞給像
<button>
這樣的元素來處理事件。 - 事件處理器必須被傳遞,而不是被呼叫!
onClick={handleClick}
,而不是onClick={handleClick()}
。 - 你可以單獨或內嵌定義事件處理器函式。
- 事件處理器定義在元件內部,因此它們可以訪問 props。
- 你可以在父元件中宣告一個事件處理器,並將其作為 prop 傳遞給子元件。
- 你可以使用應用程式特定的名稱定義你自己的事件處理器 props。
- 事件會向上傳播。在第一個參數上呼叫
e.stopPropagation()
可以防止這種情況。 - 事件可能會有不需要的預設瀏覽器行為。呼叫
e.preventDefault()
可以防止這種情況。 - 從子處理器明確呼叫事件處理器 prop 是傳播的一個很好的替代方案。
挑戰 1之 2: 修復一個事件處理器
點擊此按鈕應該在白色和黑色之間切換頁面背景。但是,當你點擊它時,什麼也沒有發生。修復這個問題。(不用擔心 handleClick
內部的邏輯——那部分沒有問題。)
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Toggle the lights </button> ); }