Svelteのイベント制御（バブリングについて）



# はじめに
svelteドキュメントの`createEventDispatcher`の章に「コンポーネントイベントはバブリングしません。」という[記述があります](https://svelte.jp/docs#run-time-svelte-createeventdispatcher)。そうなんだ〜と呼び飛ばしそうになったのですが、そもそもバブリングって普段意識していないな？と思ったため、まとめてみました。

# 結論
- DOMイベントのバブリングはイベントオプション（stopPropagation,self etc）で制御する
- コンポーネントイベントはバブリングされない

# そもそもイベントのバブリングとは？
要素上でイベントが発生すると、最初にその要素上のハンドラが実行され、次にその親要素のハンドラが実行され、さらに他の祖先を実行すること。

``` html
<main on:click={() => console.log('main')}>MAIN  
  <div on:click={() => console.log('div')}>DIV  
    <p on:click={() => console.log('p')}>P</p>  
  </div>
</main>
  
<style>  
    * {  
        margin: 10px;  
        border: 1px solid blue;  
    }  
</style>
```

![スクリーンショット 2023-02-13 0.49.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/f39442b1-46ac-05c7-6771-455ce6610166.png)
上の図でp要素をクリックすると以下のようなコンソール結果となります。

![スクリーンショット 2023-02-13 10.20.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/91520af9-d80c-f2c0-4b4c-7c916fdcc69d.png)
p要素で発火したイベントがdiv、mainまで伝播して、親要素のコンソールも出力されています。このような動作をバブリングと言われています。


# DOMイベントのバブリングを防ぐには？
svelteには[いくつかイベント修飾子が用意されています](https://svelte.jp/docs#template-syntax-element-directives-on-eventname)。バブリングは子コンポーネントで`stopPropagation`を利用する、もしくは親コンポーネントで`self`を利用することで防ぐことができます。

```html
<main on:click={() => console.log('main')}>MAIN  
  <div on:click={() => console.log('div')}>DIV  
    <p on:click|stopPropagation={() => console.log('p')}>P</p>  
  </div>
</main>
```
`stopPropagation`を利用したパターンです。`event.stopPropagation()`を呼び出し、イベントが次の要素に到達するのを防いでくれます。

```html
<main on:click|self={() => console.log('main')}>MAIN  
  <div on:click|self={() => console.log('div')}>DIV  
    <p on:click={() => console.log('p')}>P</p>  
  </div>
</main>
```
`self`を利用したパターンです。`event.target` がその要素自体だった場合のみハンドラをトリガします。イベント自体は親要素まで伝達しているため、main要素の`self`を抜くとイベントが発火します。


# コンポーネントイベントのバブリングを防ぐには？
そもそも[コンポーネントイベントはバブリングしない仕様](https://svelte.jp/docs#run-time-svelte-createeventdispatcher)になっています。そのため、バブリングを意識してイベント制御する必要はありません。
逆にバブリングされないため入れ子になったコンポーネントのイベントを親コンポーネントに伝播させるには、子コンポーネントのイベントをディスパッチして、親でリッスンする必要があります。

孫コンポーネントのイベントを親コンポーネントでリッスンするには以下のような書き方になります。


孫コンポーネント
```html:P.svelte
<script>  
  import { createEventDispatcher } from 'svelte';  
  const dispatch = createEventDispatcher();  
  
  function clickElement() {  
    console.log('P');  
    dispatch('pClick');  
  }  
</script>  
<p on:click={clickElement}>P</p>
```
`dispatch('pClick');`によって、孫コンポーネントのイベントを親コンポーネントにディスパッチしてあげます。

子コンポーネント
```html:Div.svelte
<script lang="ts">  
  import P from './P.svelte'  
</script>  
  
<div>DIV  
  <P on:pClick/>  
</div>
```
中継しているだけの子コンポーネントではdispatchを省略しています。

親コンポーネント
```html:Main.svelte
<script lang="ts">  
  import Div from './Div.svelte'  
  
  function catchEvent() {  
    console.log('Main')  
  }  
</script>  
  
<main>MAIN  
  <Div on:pClick={catchEvent}/>  
</main> 
```


### おまけ
```html:P.svelte
<script>  
  import { createEventDispatcher } from 'svelte';  
  const dispatch = createEventDispatcher();  
  
  function clickElement() {  
    console.log('P');  
    dispatch('pClick');  
  }  
</script>  
<div on:click={() => console.log('div')}>DIV
  <p on:click={clickElement}>P</p>
</div>
```

`on:click`自体はDOMイベントですが、ディスパッチすることでsvelte内部では[カスタムイベント](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)として扱われるためバブリングしません。試しに孫コンポーネント内で要素を入れ子にするとバブリングは発生しますが、親コンポーネントではバブリングしない挙動が確認できます。





# まとめ
- イベントが親要素へ順に伝播していくことをイベントのバブリングという
- イベントのバブリングを防ぐにはイベントオプション（stopPropagationやself）を使用する
- コンポーネントイベントはバブリングしない