5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-02-13

はじめに

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>

スクリーンショット 2023-02-13 0.49.19.png
上の図でp要素をクリックすると以下のようなコンソール結果となります。

スクリーンショット 2023-02-13 10.20.46.png
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を抜くとイベントが発火します。

コンポーネントイベントのバブリングを防ぐには?

そもそもコンポーネントイベントはバブリングしない仕様になっています。そのため、バブリングを意識してイベント制御する必要はありません。
逆にバブリングされないため入れ子になったコンポーネントのイベントを親コンポーネントに伝播させるには、子コンポーネントのイベントをディスパッチして、親でリッスンする必要があります。

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

孫コンポーネント

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');によって、孫コンポーネントのイベントを親コンポーネントにディスパッチしてあげます。

子コンポーネント

Div.svelte
<script lang="ts">  
  import P from './P.svelte'  
</script>  
  
<div>DIV  
  <P on:pClick/>  
</div>

中継しているだけの子コンポーネントではdispatchを省略しています。

親コンポーネント

Main.svelte
<script lang="ts">  
  import Div from './Div.svelte'  
  
  function catchEvent() {  
    console.log('Main')  
  }  
</script>  
  
<main>MAIN  
  <Div on:pClick={catchEvent}/>  
</main> 

おまけ

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内部ではカスタムイベントとして扱われるためバブリングしません。試しに孫コンポーネント内で要素を入れ子にするとバブリングは発生しますが、親コンポーネントではバブリングしない挙動が確認できます。

まとめ

  • イベントが親要素へ順に伝播していくことをイベントのバブリングという
  • イベントのバブリングを防ぐにはイベントオプション(stopPropagationやself)を使用する
  • コンポーネントイベントはバブリングしない
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?