Vueの非同期コンポーネント

programing

公式リファレンス:
https://jp.vuejs.org/v2/guide/components-dynamic-async.html

Vueでコンポーネントを作っていくと、単純に部品となるようなコンポーネントではあまり問題にならないが、 部品をまとめるコンポーネントを作るときなどに、あまりにも規模が大きくなりすぎることがある。

例えば、同じクラス(親)を継承した子供クラスのオブジェクトの内容を表示したいときなどでは、 無策で書くとその時に必要のないコンポーネントまでクライアントにダウンロードさせることになってしまう。

draw1

draw1

上記の図のように、「商品」を継承した子供クラスが3つあり、これを一つの画面にまとめて表示させたいとする。この時、 共有部分は問題にならないが、各子供クラス特有の部分が問題に成る。

例えば、「乗り物」クラスのオブジェクトの情報を表示したい時、本来であれば「乗り物」クラスの内容を表示するコンポーネントのみを クライアントはダウンロードすべきだ。しかし、普通にVueを描いていくと、本来必要のない「食べ物」や「本」用に用意したコンポーネントも クライアントにダウンロードすることを要求してしまう。

<template>
<div class="hello">
  <components :is="child"></components>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

import Vehicle from './Vehicle.vue';
import Food from './Food.vue';
import Book from './Book.vue';

@Component({
  components: {
    Vehicle,
    Food,
    Book,
  }
})
export default class Page extends Vue {
  private child: object;

  public created(){
    ....
  }
}
</script>

上記のように書いた場合、クライアントは最初に Vehicle, Food, Book の3つのコンポーネントを サーバーからダウンロードし、表示するべきコンポーネントのみを child に代入して表示させる。

実際には、Vehicle を表示するだけの時に FoodBook も必要ない。必要があれば 必要な時に必要な部分のみをサーバーに要求するほうが良い。

これを、 非同期コンポーネント が解決してくれる。

非同期コンポーネントの基本

コンポーネントを必要な時だけ、必要な分だけ要求することは一旦忘れて、まずは基本から。

非同期コンポーネントは、子コンポーネントの指定に、そのコンポーネントを生成する関数(公式リファレンス内ではファクトリ関数と呼ばれている) を指定することで定義する。

これは見たほうが早い。

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component({
  components: {
    Vehicle: () => {/* ここにVheicleコンポーネントを生成するコードを書く */}
  }
})
export default class Page extends Vue {
  private child: object;

  public created(){
    ....
  }
}
</script>

上記のように、子コンポーネントである Vheicle を直接指定するのではなく、 Vheicle と名前を付けた Vheicle を生成する関数を親に渡す。

親は、この関数を必要になったときにはじめて実行し、その場で子コンポーネントの生成を試みる。

これが非同期コンポーネントの基本。子コンポーネントを生成するファクトリ関数の中身は、好きに書いて良い。例えば、

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

import Vehicle from './Vehicle.vue';

@Component({
  components: {
    Vehicle: () => {
	  console.log('start creating Vcheicle!!!!');
      return Vehicle;
    }
  }
})
export default class Page extends Vue {
  private child: object;

  public created(){
    ....
  }
}
</script>

と書いて、 Vehicle 生成時にコンソールに出力することも出来る。最終的に、目的のコンポーネントオブジェクトが返れば良い。

分割ダウンロード

では、目的であるコンポーネントを必要な時だけ、必要な分だけ要求する部分について。

非同期コンポーネントの基本 で書いたやり方では、最初に import Vehicle from './Vehicle.vue'; を 書いてしまったために、このコンポーネントを要求すると同時に Vechicle をサーバーに要求してしまう。(生成が非同期になっただけ)

なので、 import の部分を関数の中に放り込んでやる。こうすれば、親コンポーネントは必要になった時にはじめて子コンポーネントを 生成するファクトリ関数を呼び出し、そのなかにある import によって初めてサーバーに必要なコンポーネントを要求するようになる。

typescript(javascript)には、静的なimportの他に、動的なimportが用意されており、コード内にimportを埋め込む際にはこちらを使う。

よって、以下のようになる。

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component({
  components: {
    Vehicle: () => (import('./Vehicle.vue'))
  }
})
export default class Page extends Vue {
  private child: object;

  public created(){
    ....
  }
}
</script>

これで、 Vechicle が必要な時にクライアントがサーバーに要求するようになった。

おまけ: 循環参照にも使われる

https://jp.vuejs.org/v2/guide/components-edge-cases.html#%E5%BE%AA%E7%92%B0%E5%8F%82%E7%85%A7

非同期コンポーネントは循環参照したいときにも使われる。

循環参照とは、

  1. コンポーネントAがコンポーネントBを子コンポーネントとする
  2. コンポーネントBがコンポーネントAを子コンポーネントとする

の2つが同時に存在している状態だ。

普通に書いたのでは、Vueは循環参照を勝手に解釈して解決してくれない。そこで、非同期コンポーネントを利用して、 使用時に解決できるようにこちらがVueに教えてあげないといけない。