はじめに
svelteドキュメントのcreateEventDispatcher
の章に「コンポーネントイベントはバブリングしません。」という記述があります。そうなんだ〜と呼び飛ばしそうになったのですが、そもそもバブリングって普段意識していないな?と思ったため、まとめてみました。
結論
- DOMイベントのバブリングはイベントオプション(stopPropagation,self etc)で制御する
- コンポーネントイベントはバブリングされない
そもそもイベントのバブリングとは?
要素上でイベントが発生すると、最初にその要素上のハンドラが実行され、次にその親要素のハンドラが実行され、さらに他の祖先を実行すること。
<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>
上の図でp要素をクリックすると以下のようなコンソール結果となります。
p要素で発火したイベントがdiv、mainまで伝播して、親要素のコンソールも出力されています。このような動作をバブリングと言われています。
DOMイベントのバブリングを防ぐには?
svelteにはいくつかイベント修飾子が用意されています。バブリングは子コンポーネントでstopPropagation
を利用する、もしくは親コンポーネントでself
を利用することで防ぐことができます。
<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()
を呼び出し、イベントが次の要素に到達するのを防いでくれます。
<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
を抜くとイベントが発火します。
コンポーネントイベントのバブリングを防ぐには?
そもそもコンポーネントイベントはバブリングしない仕様になっています。そのため、バブリングを意識してイベント制御する必要はありません。
逆にバブリングされないため入れ子になったコンポーネントのイベントを親コンポーネントに伝播させるには、子コンポーネントのイベントをディスパッチして、親でリッスンする必要があります。
孫コンポーネントのイベントを親コンポーネントでリッスンするには以下のような書き方になります。
孫コンポーネント
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function clickElement() {
console.log('P');
dispatch('pClick');
}
</script>
<p on:click={clickElement}>P</p>
dispatch('pClick');
によって、孫コンポーネントのイベントを親コンポーネントにディスパッチしてあげます。
子コンポーネント
<script lang="ts">
import P from './P.svelte'
</script>
<div>DIV
<P on:pClick/>
</div>
中継しているだけの子コンポーネントではdispatchを省略しています。
親コンポーネント
<script lang="ts">
import Div from './Div.svelte'
function catchEvent() {
console.log('Main')
}
</script>
<main>MAIN
<Div on:pClick={catchEvent}/>
</main>
おまけ
<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内部ではカスタムイベントとして扱われるためバブリングしません。試しに孫コンポーネント内で要素を入れ子にするとバブリングは発生しますが、親コンポーネントではバブリングしない挙動が確認できます。
まとめ
- イベントが親要素へ順に伝播していくことをイベントのバブリングという
- イベントのバブリングを防ぐにはイベントオプション(stopPropagationやself)を使用する
- コンポーネントイベントはバブリングしない