本篇筆記摘錄自官方文件 Responding to Events
在 React 裡, 我們可以對 JSX 加上事件監聽器 ( event handlers )。
事件監聽器是由我們自行編寫一個函式,用來回應使用者的互動,像是 click、hover 或是 focus 表單 input … 等。
Adding event handlers
如果想要加入事件監聽器,必須要先定義一個函式,並且以 prop 的方式傳入合適的 JSX tag。
以下範例為一個尚未加入事件監聽器的按鈕:
( 將左側拉桿向右移動,可以看見程式碼的部分,拉開後點擊左上角三條線漢堡圖示可以看見完整檔案,右邊則為輸出結果。 )
透過以下三個步驟,可以把它變成一個,透過使用者點擊,傳遞訊息給使用者的按鈕:
- 在
Button
元件中宣告handleClick
函式 - 在函式內寫邏輯 ( 使用
alert
顯示訊息) - 在
<button>
加上onClick={handleClick}
可以透過點擊右側輸出結果的按鈕測試效果:
在剛剛的過程中,我們首先定義了handleClick
函式,並且以 prop 的方式傳入<button>
,handleClick
就是一個事件監聽器。
事件監聽器有以下兩個特點:
- 通常會在元件內定義。
- 命名慣例:以
handle
為命名開頭,搭配事件的類型。
根據命名慣例,我們常常能看到像是這樣的命名:onClick={handleClick}
或是onMouseEnter={hadleMouseEnter}
… 等。
你也可以直接在 JSX 內 ( inline )定義它們:
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
或是使用箭頭函式,讓它們更為簡潔:
<button onClick={() => {
alert('You clicked me!');
}}>
以上三種方法完全是相同的。
對於那些比較簡短的函式而言,使用 inline 的方式是非常方便的。
【 Pitfall 】
本篇的 Pitfall,個人覺得是事件監聽器非常重要的使用觀念。
函式必須以傳入的方式傳進事件監聽器,而不是呼叫。
仔細看上圖的範例,會發現兩者外觀上差異非常小,但它們是完全不同的兩件事。
以左邊的範例來說,handleClick
被當作onClick
事件監聽器傳入。
這會讓 React 記住這件事,並且只會在使用者點擊按鈕時觸發此函式。
右邊的函式多了()
,這代表函式會在渲染期間立即觸發,不需要透過任何使用者的點擊,這是因為在 JSX 裡,放在{}
裡的 JavaScript 會立即執行。
如果不清楚
{}
的作用,可以參考這篇 JSX 與 JavaScript 的大括號好朋友。
使用 inline 寫法時也是同樣的原理:
以右邊的寫法來說,並不會在使用者點擊時跳出alert
,而是每一次元件渲染時,它都會被觸發。
如果你希望使用 inline 的方式來定義事件監聽器,請把它寫成一個匿名函式,像是範例左側的寫法。
總結上面兩種範例,不管是哪一種,我們要使用 prop 傳入的都是函式。
Reading props in event handlers
因為事件監聽器是宣告在元件之內,所以它們可以取得元件的 props。
以下為一個點擊監聽範例,alert
的訊息內容源自於我們給定的 props,你可以試著改變的message
內容,觀察輸出結果的不同之處:
Passing event handlers as props
在有些情況下,我們可能會希望由父元件來指定子元件的事件監聽器。
以下範例藉由傳入不同的函式,使Button
元件能夠實現不同的內容:
在這個範例裡,Toolbar
渲染PlayButton
元件與UploadButton
元件:
PlayButton
傳入onClick
prop 到Button
,內容為handleClick
。UploadButton
傳入onClick
prop 到Button
,內容為() => alert('Uploading!')
。
最終,Button
元件會收到一個名為onClick
的 prop,並且把 prop 傳給瀏覽器內建的<button>
(onClick={onClick}
),這個行為會告訴 React,當使用者點擊時,請執行onClick
內的函式。
Naming event handler props
像是<button>
或是<div>
這種內建元件,只會支援瀏覽器的事件名稱,像是onClick
。
如果是自己建立的元件,則可依照個人的喜好為事件監聽器的 props 命名。
依照命名慣例,事件監聽器的 props 通常以on
為開頭,後面接大寫字母。
舉例來說,Button
元件的onClick
prop,也可以叫做onSmash
:
在這個範例裡,從<button onClick={onSmash}>
可以看出,<button>
仍然需要以onClick
來當 prop 名稱,但是我們自己建立的Button
元件,則可依照喜好命名。
當你的元件支援多種互動時,我們可以用好的命名方式來辨別它們,以下方的例子來說,Toolbar
元件接收了onPlayMovie
與onUploadImage
事件監聽器:
請注意,App
元件並不需要知道Toolbar
會怎麼使用onPlayMovie
與onUploadImage
,這是由Toolbar
負責的。
Toolbar
將它們作為onClick
監聽器,傳給了內部的Button
元件,為 props 命名,可以讓程式碼更為彈性。
Event propagation
事件監聽器也會捕捉到子層的事件,我們通常稱它為buubles
或是propagates
,中文為“冒泡”的意思。
以下面這個範例來說,<div>
包覆了兩個<button>
,而它們三個各自都有onClick
監聽器,請猜猜看,點擊按鈕後會觸發哪一個事件呢?
猜完就用力的點擊看看吧!
從實際操作結果可看出,點擊按鈕後,會先觸發它本身的onClick
,接著再觸發<div>
的onClick
,因此會依序出現兩種訊息。
如果你只有點擊Toobar
的部分,則只會觸發<div>
的onClick
,不會再觸發子元素的事件,因為事件是由內而外擴散,因此被稱為冒泡。
【 Pitfall 】
React 內所有的事件都會冒泡,除了onScroll
,onScroll
只會運作在你設定的 JSX tag 上。
Stopping propagation
事件監聽器接收 event object 做為它的唯一引數 ( argument ),依照命名慣例,通常會命名為e
,意思為 event,裡面包含了事件的資訊。
event object 也可以幫助我們停止冒泡 ( stop propagation ) 至父層,方法為呼叫e.stopPropagation()
,就像下方範例中,Button
元件所做的事:
當你點擊按鈕的時候:
1. React 呼叫傳遞給<button>
的onClick
監聽器。
2. 定義在Button
裡的監聽器會做以下兩件事:
- 呼叫
e.stopPropagation()
,避免冒泡發生。 - 呼叫由
Toolbar
元件傳入的onClick
函式。
3 . 定義在Toolbar
元件內的函式,展現按鈕本身的alert
。
4. 由於冒泡被停止了,因此不會執行父層<div>
的onClick
。
從右邊輸出結果可知,現在點擊按鈕只會跑出單一alert
( 來自<button>
),沒有再發生像之前連續跑出訊息的狀況。
【 DEEP DIVE 】
在某些情形下,我們可能會需要使用到捕獲 ( Capture )。
舉例來說,如果想要紀錄每一次的點擊用來分析,我們可以在父層的事件名稱後加上Capture
:
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
事件冒泡會經過以下三個階段:
- 向下移動,呼叫所有的
onClickCapture
監聽器。 - 運行點擊元素的
onClick
監聽器。 - 向上移動,呼叫所有的
onClick
監聽器。
如果覺得很難想像的話,可以將前一個範例裡Toolbar
的onClick
改為onClickCapture
,再試著操作右側的輸出結果,搭配實際操作將會較好理解。
捕獲事件較常用於分析,通常不太會需要在app
內使用到它們。
Passing handlers as alternative to propagation
有些瀏覽器事件會有預設的行為,像是<form>
的 submit event,預設為點擊按鈕後重新載入頁面:
你可以在 event object 上呼叫e.preventDefault()
來阻止預設行為:
要小心不要搞混e.stopPropagation()
與e.preventDefault()
,它們之間是沒有任何關聯的:
e.stopPropagation()
用來阻止冒泡。e.preventDefault()
用來阻止瀏覽器的預設行為。
Can event handlers have side effects?
事件監聽器是用來放置 side effects 的最佳地點。
與用來渲染的函式不同,事件監聽器不需要是 Pure 的,所以它很適合拿來放一些需要改變的事情。
例如:改變input
的值,以回應使用者輸入,或是透過按鈕的點擊,改變表單回應使用者。
為了改變資訊,首先我們會需要一些方法來儲存這些資訊,在 React 裡,資訊是以狀態 ( state ) 的方式來儲存,簡單來說,狀態就是元件的記憶體,我們將會在下一篇介紹如何使用狀態來儲存資訊。