2-7 與使用者互動 - React 中的事件處理

在上一個單元中我們已經完成整個計數器的畫面,並且把計數器建立成一個 React 元件,但是目前只有畫面還沒有實際的功能。在這個單元中將回學到如何在 React 中進行事件處理,包含在 JSX 中綁定 DOM 事件,以及在 React 元件中定義事件處理的方法。

在 React 元件中使用變數#

在前面的單元中,我們曾經提到如何把變數放到 JSX 中呈現,現在雖然我們已經把計數器包成了 React 元件,但它本質上還是一個回傳 JSX 的 JavaScript 函式,因此如果我們想要在 React 元件帶入變數的做法是一樣的。

現在我們先把上一個單元完成的 <Counter /> 元件中的數字部分改成用變數來呈現,你可以 Fork 上一個單元的程式碼繼續開始。

在一個單元中,因為 <Counter /> 元件是直接回傳一個 JSX,所以在箭頭函式中,我們可以直接在 => 後面回傳 JSX 元件;但現在因為我們要在函式內加入變數,所以要改回最一般箭頭函式的寫法,也就是 () => {},這時候就可以在這個函式中加入計數器的變數,像是這樣:

const Counter = () => {
const count = 256;
return (
<div className="container" style={{ margin: '0 30px' }}>
<div className="chevron chevron-up" />
<div className="number">{count}</div>
<div className="chevron chevron-down" />
</div>
);
};
// 使用 <Counter /> 來帶入 React 元件
ReactDOM.render(<Counter />, document.getElementById('root'));

把變數帶入 JSX 的方法一樣,只是現在我們把變數 count 宣告在 Counter 這個 component 裡面:

imgur

info

💡 在 JSX 中因為它本質上是 JavaScript,所以如果想在 JSX 內撰寫註解的話,可以把註解寫在 {} 裡面,像是這樣 {/* 這裡是註解 */}

在 React 元件中綁定事件監聽器#

為了讓計數器能夠運作,現在我們需要一個方法來改變 count 這個變數。先前使用原生的 JavaScript 時,是使用 addEventListener('click', ...) 的方法,在 React 元件中則是會透過 onClick 直接把事件綁定在 JSX 上面。例如,現在想要在「向上箭頭」的按鈕綁定點擊事件時,可以在 JSX 中這樣寫:

<div className="chevron chevron-up" onClick={/*...*/} />

onClick={...}{...} 內要放的就是點擊後要做什麼處理, 一般稱作事件處理器(event handlers),它會是一個函式。我們來試試看,當使用者點擊向上箭頭的時候,在 console 中顯示訊息,像是這樣:

const Counter = () => {
const count = 256;
return (
<div className="container" style={{ margin: '0 30px' }}>
{/* 透過 onClick 綁定使用者的點擊事件 */}
<div
className="chevron chevron-up"
onClick={() => {
console.log(`current Count is ${count}`);
}}
/>
<div className="number">{count}</div>
<div className="chevron chevron-down" />
</div>
);
};

這時候從瀏覽器的 console 視窗中,可以看到當點擊向上箭頭時會有訊息呈現,而向下箭頭因為還沒有註冊事件,因此點擊後不會有反應。

試著修改變數,看看畫面會不會改變#

接下來會很直覺的想說,既然現在已經可以監聽使用者的點擊事件,那要改變數字就沒問題了,只需要像先前原生 JavaScript 的寫法一樣,在使用者點擊畫面時,把 count + 1 就可以了吧!?

於是我們把 const count = 256 改成 let count = 256count 可以重新被賦值,在 onClick 的時候讓這個 count 變數的值加 1,像這樣:

const Counter = () => {
let count = 256;
return (
<div className="container" style={{ margin: "0 30px" }}>
<div
className="chevron chevron-up"
onClick={() => {
count = count + 1; // 每次點擊時都讓 count + 1
console.log(`current Count is ${count}`);
}}
/>
<!-- ... --->
</div>
);
};

但實際測試的結果你會發現和我們預期的卻不太一樣,你會發現,當我們點擊箭頭時,雖然在瀏覽器 console 顯示的 count 數字有持續增加,但是畫面的數字卻是變也不變!為什麼會這樣子呢?

Imgur

為什麼畫面沒有改變#

一開始碰到這個問題會有些困惑,但了解原因之後並不難懂。因為雖然 count 的數字更新了,但 React 並不知道數字有更新,所以它不會去重新轉譯瀏覽器的畫面,這個感覺有點類似先前在我們使用原生 JavaScript 撰寫計數器時,雖然在使用者點擊按鍵後有把 count + 1 ,但最後沒有使用 numberElement.textContent 把更新後的值重新給回網頁的情況。

當然,我們不能只是說說而已,要確定是不是真的因為 React 元件的畫面沒有更新(重新轉譯)才使得畫面上的數字沒有改變的話,我們可以在 JSX 中使用 {console.log('render')} 來看看,因為只要 React 有需要更新畫面的話,這個 JSX 就會被重新執行。因此,我們可以像這樣寫:

const Counter = () => {
let count = 256;
return (
<div className="container" style={{ margin: '0 30px' }}>
{/* 只要 React 有更新畫面的話,這個 JSX 就會被重新執行 */}
{console.log('render')}
{/*... */}
</div>
);
};

打開 console 視窗你應該會看到如下的訊息,一開始什麼都不做的時候出現了 "render",接著每點擊一次按鈕,就出現寫在事件處理器中的訊息:

"render"
"current Count is 257"
"current Count is 258"
...

這個意思表示,我們的畫面只有被轉譯了一次,即使之後 count 變數更新了,畫面也沒有跟著更新(重新轉譯)。那麼接下來的問題就是,要怎麼讓 React 知道,我們的數字改變了,並請它幫我們更新畫面呢?

在下一個單元中我們會使的到第一個 React Hook,讓 React 能夠知道我們的變數更新了,請 React 幫我們更新畫面。

換你了:在 React 元件中綁定事件#

現在,換你試著實際操作看看,在這裡我們使用的是點擊事件,因此綁定了 onClick 元素,但如同 HTML 一樣,我們還可以綁定各種其他的 DOM 事件,像是鍵盤的 onKeyPressonKeyUp;表單的 onChangeonInputonSubmit 等等。

唯一要稍微留意的是,這些事件名稱大多和 HTML 原生綁定事件的名稱相似,只是因為在 JSX 中 HTML 標籤的屬性慣例上是使用小寫駝峰來命名,因此原生的 onclick 在 JSX 中會變成 onClickonsubmit 則變成 onSubmit 等等。你可以在 React 官方文件的 SyntheticEvent(https://reactjs.org/docs/events.html)中找到幾乎所有要用的事件。

實作過程中如果有任何不清楚的地方,都可以到下方的連結查看原始碼,或透過 Github 說明頁點擊連結「在 React 元件中綁定事件」:

https://codepen.io/PJCHENder/pen/pogbWQr

Imgur