【 React 】事件監聽器 ( Responding to Events )

Jamie Lo
9 min readFeb 10, 2023

--

Photo by Markus Spiske on Unsplash

本篇筆記摘錄自官方文件 Responding to Events

在 React 裡, 我們可以對 JSX 加上事件監聽器 ( event handlers )。

事件監聽器是由我們自行編寫一個函式,用來回應使用者的互動,像是 click、hover 或是 focus 表單 input … 等。

Adding event handlers

如果想要加入事件監聽器,必須要先定義一個函式,並且以 prop 的方式傳入合適的 JSX tag。

以下範例為一個尚未加入事件監聽器的按鈕:

( 將左側拉桿向右移動,可以看見程式碼的部分,拉開後點擊左上角三條線漢堡圖示可以看見完整檔案,右邊則為輸出結果。 )

透過以下三個步驟,可以把它變成一個,透過使用者點擊,傳遞訊息給使用者的按鈕:

  1. Button元件中宣告handleClick函式
  2. 在函式內寫邏輯 ( 使用 alert 顯示訊息)
  3. <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傳入onClickprop 到Button,內容為handleClick
  • UploadButton傳入onClickprop 到Button,內容為() => alert('Uploading!')

最終,Button元件會收到一個名為onClick的 prop,並且把 prop 傳給瀏覽器內建的<button>(onClick={onClick}),這個行為會告訴 React,當使用者點擊時,請執行onClick內的函式。

Naming event handler props

像是<button>或是<div>這種內建元件,只會支援瀏覽器的事件名稱,像是onClick
如果是自己建立的元件,則可依照個人的喜好為事件監聽器的 props 命名。

依照命名慣例,事件監聽器的 props 通常以on為開頭,後面接大寫字母。

舉例來說,Button元件的onClickprop,也可以叫做onSmash

在這個範例裡,從<button onClick={onSmash}>可以看出,<button>仍然需要以onClick來當 prop 名稱,但是我們自己建立的Button元件,則可依照喜好命名。

當你的元件支援多種互動時,我們可以用好的命名方式來辨別它們,以下方的例子來說,Toolbar元件接收了onPlayMovieonUploadImage事件監聽器:

請注意,App元件並不需要知道Toolbar會怎麼使用onPlayMovieonUploadImage,這是由Toolbar負責的。

Toolbar將它們作為onClick監聽器,傳給了內部的Button元件,為 props 命名,可以讓程式碼更為彈性。

Event propagation

事件監聽器也會捕捉到子層的事件,我們通常稱它為buubles或是propagates,中文為“冒泡”的意思。

以下面這個範例來說,<div>包覆了兩個<button>,而它們三個各自都有onClick監聽器,請猜猜看,點擊按鈕後會觸發哪一個事件呢?
猜完就用力的點擊看看吧!

從實際操作結果可看出,點擊按鈕後,會先觸發它本身的onClick,接著再觸發<div>onClick,因此會依序出現兩種訊息。

如果你只有點擊Toobar的部分,則只會觸發<div>onClick,不會再觸發子元素的事件,因為事件是由內而外擴散,因此被稱為冒泡。

【 Pitfall 】

React 內所有的事件都會冒泡,除了onScrollonScroll只會運作在你設定的 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>

事件冒泡會經過以下三個階段:

  1. 向下移動,呼叫所有的onClickCapture監聽器。
  2. 運行點擊元素的onClick監聽器。
  3. 向上移動,呼叫所有的onClick監聽器。

如果覺得很難想像的話,可以將前一個範例裡ToolbaronClick改為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 ) 的方式來儲存,簡單來說,狀態就是元件的記憶體,我們將會在下一篇介紹如何使用狀態來儲存資訊。

--

--

Jamie Lo

正在往前端這個知識量爆炸的黑洞前行,內容多為平時的筆記整理,希望也能幫助到同樣在這條道路上前進的人💪