在設計畫面互動的時候,有時候需要撰寫一些事件處理,這邊將介紹其中的原理與在React中的一些小知識。

簡單介紹

要專業的處理event事件,就必須要了解擷取與冒泡事件,概念簡單但是特別重要。
根據上圖,我們可以看到事件處理主要分為三個階段。

  1. 擷取階段
  2. 目標階段
  3. 冒泡階段

如果你覺得太複雜的話,你可以想像你是一位路跑選手,window是起點與終點,目標是轉則點,你的目標是蒐集事件,而事件又分為冒泡事件和擷取事件,如果他是擷取事件,那你應該是在抵達轉折點之前蒐集,如果是冒泡事件則是在轉折之後蒐集。

理解這個概念之後就可以開始認識擷取與冒泡了。

DOM元素事件

addEventListener的第二個參數可以控制這個參數是屬於擷取還是冒泡事件,如果標記這個事件屬於擷取事件,那麼這個EventListener就會在擷取階段被trigger。

1
2
3
4
5
6
7
8
9
10
11
12
13
<form>
FORM
<div>DIV
<p>P</p>
</div>
</form>

<script>
for(const element of document.querySelectorAll('*')) {
element.addEventListener("click", e => alert(`Capturing: ${element.tagName}`), true);
element.addEventListener("click", e => alert(`Bubbling: ${element.tagName}`));
}
</script>

React元素事件

DOM element與React element的事件處理是兩個不一樣卻類似的東西。
主要差異在:

  1. 事件的名稱在 React 中都是 c,而在 HTML DOM 中則是小寫。
  2. 事件的值在 JSX 中是一個 function,而在 HTML DOM 中則是一個 string。

SyntheticEvent

React中的event為根據W3C定義的SyntheticEvent,它也包含了瀏覽器的原生事件,例如:stopPropagation。

camelCase

1
2
3
<button onClick={activateLasers}>
Activate Lasers
</button>

避免瀏覽器預設行為

DOM element可以使用return false辦到,React可以使用preventDefault

1
2
3
4
5
6
7
8
9
10
11
12
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}

return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}

冒泡事件

冒泡顧名思義就是泡泡冒上來的意思,因此是bottom-up的方式傳遞事件。
幾乎大部分的事件都是屬於冒泡事件,當一個元素被trigger時,自己本身的event handler會先觸發,再往上冒泡trigger parent的event handler。

以下以DOM event的onclick為例解釋冒泡事件:

1
2
3
4
5
6
7
8
9
10
<!--
Bubbling: <p> -> <div> -> <form>
-->
<form onclick="alert('form')">
FORM
<div onclick="alert('div')">
DIV
<p onclick="alert('p')">P</p>
</div>
</form>

也就是往上冒泡的過程,甚至可以到最外層的html,幾乎所有的parent都知道你在做什麼了,可是我不想讓parent知道我在做什麼
可以使用到event.stopPropagation() 來停止任何的冒泡和擷取事件傳遞。
但是需要注意是在什麼階段使用它,才能夠妥善達到預期的效果。

擷取事件

在擷取階段的時候,擷取事件會被trigger。
儘管大部分的事件都屬於冒泡事件,但是我們可以自定義事件為擷取事件,這樣在top-down的過程就可以處理需要觸發的事件了。
這邊來介紹一個React提供但是很多人不知道的東西,叫做:onClickCapture。
onClick是在冒泡階段會trigger的事件,但是如果你想要在擷取階段trigger的話,可以使用:onClickCapture

實作例子

如果你要製作Card,點擊卡片會開啟新視窗,但是上面又有其他按鈕時,這個時候你就需要針對事件去做處理。

1
2
3
4
5
6
7
8
<Card key={card.id} onClick={open('/card', '_blank')}>
<div onClick={(event) => {
event.stopPropagation();
putCollection(card.id);
}}>
// ...
</div>
</Card>

補充資料

  1. https://javascript.info/bubbling-and-capturing
  2. https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwjw9f6w7InyAhXJGaYKHRXRBicQFjABegQIBRAD&url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fju5fz&usg=AOvVaw0dwXidXILLy-e42TMYKUFu