Written by TSUYOSHI

Vueで動的コンポーネントの使い方 【is属性やkeep-aliveの利用】

JavaScript PROGRAMMING Vue

本記事では、Vueにてcomponentタグとis属性を用いた「動的コンポーネント」について解説します。

この記事を書いている僕は、フロントエンドのプログラマー(フリーランス)として、これまで4年ほどのWeb制作経験があり、Vue,React,Nuxtなどフロントエンドの開発を得意としています。

この記事を読むことによって、Vueで動的コンポーネントの使い方が分かるようになります。

Vueで動的コンポーネントの使い方 【is属性やkeep-aliveの利用】

GitHubソースコード

この記事で紹介しているソースコードは、以下のGitHubに上げています。
https://github.com/it-web-life/vue_dynamic_components

componentタグを使い、is属性をbindして、動的にコンポーネントを切り替える

例えば以下のように、「Fooコンポーネント」と「Barコンポーネント」を切り替えたい時に、v-ifを使っていたとします。

▼もとのv-ifを使っているソースコード全体

<template>
  <div class="wrap">
    <h3>componentタグとis属性によるコンポーネントの動的切り替え</h3>
    <div class="tab-button">
      <button @click="current = 'Foo'">Switch Foo</button>
      <button @click="current = 'Bar'">Switch Bar</button>
    </div>
    <div class="tab-contents">
      <Foo v-if="current === 'Foo'"></Foo>
      <Bar v-else></Bar>
    </div>
  </div>
</template>

<script>
import Foo from './Foo.vue';
import Bar from './Bar.vue';

export default {
  name: 'Main',
  components: {
    Foo,
    Bar
  },
  data() {
    return {
      current: 'Foo',
    }
  }
}
</script>

動的コンポーネント(componentタグis属性bind)を使う

実はこれはシンプルに書き直すことができます。componentタグとis属性をbindして、is属性にコンポーネント名を渡すことによって同様のことができます。

▼修正前:v-ifでコンポーネントを切り分け

<div class="tab-contents">
  <Foo v-if="current === 'Foo'"></Foo>
  <Bar v-else></Bar>
</div>

▼修正後:動的コンポーネント

<div class="tab-contents">
  <comopnent :is="current"></comopnent>
</div>

修正後のコードは以下の通りです。

修正後のソースコード全体

<template>
  <div class="wrap">
    <h3>componentタグとis属性によるコンポーネントの動的切り替え</h3>
    <div class="tab-button">
      <button @click="current = 'Foo'">Switch Foo</button>
      <button @click="current = 'Bar'">Switch Bar</button>
    </div>
    <div class="tab-contents">
      <comopnent :is="current"></comopnent>
    </div>
  </div>
</template>

<script>
import Foo from './Foo.vue';
import Bar from './Bar.vue';

export default {
  name: 'Main',
  components: {
    Foo,
    Bar
  },
  data() {
    return {
      current: 'Foo',
    }
  }
}
</script>

componentタグis属性を使った対応の方が可読性もよく、シンプルでよいことが分かると思います。

毎回インスタンスが生成され直すのをkeep-aliveで抑制する

動的コンポーネントを使う時に、頻繁にコンポーネントが動的に切り替える場合は注意が必要です。コンポーネントの切り替えが発生する度にインスタンスが作り直されるからです。
つまり、毎回created()destroyed()が呼び出されることになります。

具体的な例としては、タブ機能で、タブの内容がコンポーネントになっている時などです。

▼タブ内のデータが切り替えで消えてしまい、正常に動作しないコード例

<template>
  <div class="wrap">
    <h3>keep-aliveを使ったインスタンスのキャッシュ</h3>
    <div class="tab-button">
      <button @click="current = 'Fuga'">Switch Fuga</button>
      <button @click="current = 'Hoge'">Switch Hoge</button>
    </div>
    <div class="tab-contents">
      <comopnent :is="current"></comopnent>
    </div>
  </div>
</template>

タブの切り替えが発生する度にインスタンスを作り直すため、inputタグなどがある場合に、タブを切り替える度にタブ内のデータが消えてしまうという現象が発生します。

keep-aliveを使うと内容が保持できる

これを解消するために、keep-aliveを使います。動的コンポーネントを使っている部分を「keep-alive タグ」で囲えば、動的にコンポーネントを切り替えても、コンポーネントのインスタンスが保持されるようになります。

keep-aliveタグを使うと、ライフサイクルフックが変わる。

keep-aliveを使うとインスタンスの再生成がされなくなるため、created()destroyed()が発生しなくなり、切り替え時に処理を行いたい時に対応できなくなってしまいます。そこでkeep-aliveを使った場合には、動的コンポーネントに新たにライフサイクルフックが加わるようになっています。

created()destroyed()のライフサイクルは無くなる代わりに、activated()deactivated()を使うことができるようになります。

activated() →アクティブになる時に呼び出されます
deactivated() →アクティブでなくなる時に呼び出されます

keep-aliveを使ったサンプルコード

<template>
  <div class="wrap">
    <h3>keep-aliveを使ったインスタンスのキャッシュ</h3>
    <div class="tab-button">
      <button @click="current = 'Fuga'">Switch Fuga</button>
      <button @click="current = 'Hoge'">Switch Hoge</button>
    </div>
    <div class="tab-contents">
      <keep-alive>
        <comopnent :is="current"></comopnent>
      </keep-alive>
    </div>
  </div>
</template>

<script>
import Fuga from './Fuga.vue';
import Hoge from './Hoge.vue';

export default {
  name: 'Main',
  components: {
    Fuga,
    Hoge
  },
  data() {
    return {
      current: 'Fuga',
    }
  }
}
</script>

まとめ

動的コンポーネントを使う時は、componentタグを使って、is属性にコンポーネント名を渡すことによって動的に切り替えが可能です。

また動的コンポーネントの切り替え時に毎回インスタンスの生成をしたくない時は、keep-aliveを使うことによって対応ができます。

ご参考になれば幸いです。