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作為onClickprop 傳遞給內部的Button。UploadButton將() => alert('正在上傳!')作為onClickprop 傳遞給內部的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> ); }