Vue Composition APIの使い方を解説!Vue2との違いと書き換え

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。

Composition APIでは今まで慣れ親しんできたVue2の書き方から一新されてしまったため、「使い方が分からない」と思っている方も多いのではないでしょうか。

この記事では以下の公式ドキュメントを基に、Composition APIの基本的な使い方を解説していきます。

Vue Composition APIとは

Vue Composition APIとは以下の2点のために策定された関数ベースのAPIです。

  • コードの可読性・再利用性の改善
  • 型インターフェースの改善

今までのVue2でのOptions APIでは1つのコンポーネントが複数の役割を持った際にコードが肥大化し、可読性が著しく低下するという問題がありました。

そこでComposition APIでは関心事によってコードを分割し、分割したコードを簡単にコンポーネントに注入できるようになっています。

詳しくは以下の記事で解説していますので、気になる方は是非ご覧ください。

関連記事

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。 Composition APIの使い方が分かったあとは、なぜ使うのかというところが気になりますよね。[…]

Vue Composition APIの使い方

ここからは以下の項目について解説していきます。

  • data
  • methods
  • computed
  • props・emit
  • watch
  • refs
  • ライフサイクルフック

data

Options APIではリアクティブな変数を定義するためにdataオプションを使用していましたが、Composition APIではref・reactiveという関数を使用します。

公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div>{{ count }}</div>
</template>
<script>
export default {
  data() {
    return {
      count: 1,
    };
  },
};
</script>

こちらは既に見慣れた書き方ですね。
dataオプションでリアクティブな値を定義しています。

Composition API

Composition APIでは以下の手順でリアクティブな変数を定義します。

  1. setup関数内でref・reactiveを使用してリアクティブな変数を生成
  2. ref・reactiveで生成したリアクティブな変数をsetup関数からreturn
  3. template内でsetup関数からreturnしたリアクティブな変数を利用
ロボット
Composition APIでは基本的にsetup関数内で必要なものを定義してreturnします。
<template>
  <div>{{ countRef }}</div>
  <div>{{ obj.count }}</div>
</template>

<script>
import { ref, reactive } from "vue";
export default {
  setup() {
    // refまたはreactiveを使用して
    // リアクティブな変数を生成します
    const countRef = ref(1);
    const obj = reactive({
      count: 2,
    });
    return {
      countRef,
      obj,
    };
  },
};
</script>

<style>
</style>
ロボット
Vue3からはtemplate内に1つのルート要素を置くという制約は無くなりました。

template内でこのrefとreactiveで生成したリアクティブな変数にアクセスする際には以下の点に注意してください。

  • refで生成した場合には直接アクセスする
  • reactiveで生成した場合には.(ドット)を使用してプロパティにアクセスする

refはプリミティブ、reactiveはオブジェクトのようなイメージですね。
今回のコードでは以下の部分です。

<template>
  <div>{{ countRef }}</div>
  <div>{{ obj.count }}</div>
</template>
ロボット
なるほど、でもこのrefとreactiveはどうやって使い分けるんだろう。。。

実はこのrefとreactiveの使い分け方を決めるのは時期尚早であると公式ドキュメントで言及されています。

詳しくはこちらの記事で解説していますので、気になる方はぜひご覧ください。

関連記事

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。 Vue Composition APIではリアクティブな変数を定義する際にrefかreactiveを[…]

まとめ

  • setup関数内でref・reactiveを使用してリアクティブな変数を生成する
  • ref・reactiveで生成した変数をsetup関数からreturnする
  • template内ではrefで生成した変数に直接アクセスする
  • template内ではreactiveで生成した変数に.(ドット)を使用してプロパティにアクセスする
  • ref・reactiveの使い分け方は現在策定中

methods

Options APIではmethodsオプションを使用して関数を定義していましたが、Composition APIではsetup関数から関数をreturnするだけでtemplate内で使用できるようになります。

以下で違いを見ていきましょう。

Options API

<template>
  <div>
    <button @click="decrement">-</button>
    {{ count }}
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 1,
    };
  },
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
};
</script>

Options APIではmethodsオプションの中に関数を定義していました。

Composition API

Composition APIでは以下の手順で関数を定義します。

  1. setup関数内で関数を定義
  2. 定義した関数をsetup関数からreturn
  3. template内でsetup関数からreturnした関数を利用
ロボット
template内ではOptions APIと同じように使用できます。
<template>
  <!-- template内での使い方はOptions APIと同じです -->
  <button @click="decrement">-</button>
  {{ countRef }}
  <!-- template内での使い方はOptions APIと同じです -->
  <button @click="increment">+</button>
</template>

<script>
import { ref } from "vue";
export default {
  setup() {
    const countRef = ref(0);
    // 通常の関数と同じように定義します
    const decrement = () => {
      countRef.value--;
    };
    // 通常の関数と同じように定義します
    const increment = () => {
      countRef.value++;
    };
    return {
      countRef,
      decrement,
      increment,
    };
  },
};
</script>

<style>
</style>

template内でcountRefの値を表示しています。

また、「-ボタン」と「+ボタン」からdecrement関数とincrement関数を呼び出してcountRefの値を増減させています。

今回のようにrefで定義した変数を操作する場合には以下の点に注意してください。

  • refで生成した変数を操作するには.valueを用いて値にアクセスする

今回のコードでは以下の部分です。

const decrement = () => {
  countRef.value--;
};
const increment = () => {
  countRef.value++;
};

まとめ

  • setup関数内で関数を定義してreturnする
  • template内ではsetup関数からreturnした関数をOptions APIと同じように使用できる
  • refで定義したリアクティブな変数を操作する場合には.valueを使用する

computed

Options APIで計算プロパティを定義するためにcomputedオプションを使用していましたが、Composition APIではcomputed関数を使用します。

公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div>
    {{ count }}
    <div>2倍すると: {{ doubleCount }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 1,
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    },
  },
};
</script>

Options APIではcomputedオプションで計算プロパティを定義していました。

Composition API

Composition APIでは以下の手順で計算プロパティを定義します。

  1. setup関数内でcomputed関数を使用して計算プロパティを定義
  2. 定義した計算プロパティをsetup関数からreturn
  3. template内でsetup関数からreturnした計算プロパティを利用
ロボット
template内ではOptions APIと同じように使用できます。
<template>
  {{ countRef }}
  <div>2倍すると: {{ doubleCount }}</div>
</template>

<script>
import { ref, computed } from "vue";

export default {
  setup() {
    const countRef = ref(1);

    // computed関数を使用して計算プロパティを定義します
    const doubleCount = computed(() => {
      return countRef.value * 2;
    });

    return {
      countRef,
      doubleCount,
    };
  },
};
</script>

<style>
</style>

template内でcountRefとcountRefの値を2倍にするdoubleCountを表示しています。

今回はcountRefを増減させるような処理は用意していませんが、実際にcountRefの値を増減させるとdoubleCountがそれに反応してcountRefの2倍の値を表示します。

まとめ

  • setup関数内でcomputed関数を使用して計算プロパティを定義
  • 定義した計算プロパティをsetup関数からreturn
  • template内ではOptions APIと同じように使用できる

props・emit

props・emitといえば親子間でのデータの受け渡しです。

Options APIではpropsオプションと$emit関数によって実現していましたが、Composition APIではsetup関数に渡されるpropsとcontextを使用します。

公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div>
    <ChildComponent :count.sync="count" />
  </div>
</template>

<script>
import ChildComponent from "@/components/ChildComponent";

export default {
  components: {
    ChildComponent,
  },

  data() {
    return {
      count: 1,
    };
  },
};
</script>
<template>
  <div>
    {{ count }}
    <button @click="increment">+</button>
    <div>2倍すると: {{ doubleCount }}</div>
  </div>
</template>

<script>
export default {
  props: {
    count: {
      type: Number,
      required: true,
    },
  },

  computed: {
    doubleCount() {
      return this.count * 2;
    },
  },

  methods: {
    increment() {
      this.$emit("update:count", this.count + 1);
    },
  },
};
</script>

propsオプションで親コンポーネントから値を受け取って、$emitで親コンポーネントにイベントを送出していました。

今回は子コンポーネントから送られてきた値を親コンポーネント側で更新するだけなので.syncを使用しています。

Composition API

Composition APIでは以下の手順で親子間でデータを受け渡します。

  1. 親から子にデータをバインド
  2. setup関数の第1引数にpropsが渡される
  3. setup関数の第2引数に渡されるcontextのemitで親にイベントを送出
<template>
  <ChildComponent v-model:count="countRef" />
</template>

<script>
import { ref } from "vue";
import ChildComponent from "@/components/ChildComponent";

export default {
  components: {
    ChildComponent,
  },

  setup() {
    const countRef = ref(1);

    return {
      countRef,
    };
  },
};
</script>

<style>
</style>
<template>
  {{ count }}
  <button @click="increment">+</button>
  <div>2倍すると: {{ doubleCount }}</div>
</template>

<script>
import { computed } from "vue";

export default {
  props: {
    count: {
      type: Number,
      required: true,
    },
  },

  setup(props, context) {
    // propsに親コンポーネントから渡されたデータが格納されています
    const doubleCount = computed(() => {
      return props.count * 2;
    });

    const increment = () => {
      // contextにemitが用意されています
      context.emit("update:count", props.count + 1);
    };

    return {
      increment,
      doubleCount,
    };
  },
};
</script>

<style>
</style>

ここまでは使用してきませんでしたが、setup関数は引数を2つ受け取ります。
第1引数がprops、第2引数がcontextです。
第1引数のpropsには親コンポーネントから渡されたデータが格納されています。

propsには分割代入を使用しないように注意してください。
リアクティビティが失われてしまいます。

// propsに分割代入を適用すると
// リアクティビティが失われてしまいます
setup({ count }) {
}
  • propsには分割代入を使用しない

第2引数のcontextにはOptions APIでVueインスタンスに用意されていたemit・attrs・slotsが格納されています。

今回はcontext.emitで親コンポーネントにイベントを送出しています。

contextはpropsと違って分割代入を使用しても問題ないので、以下のように書くことも可能です。

setup(props, { emit }) {
    const increment = () => {
      emit("update:count", props.count + 1);
    };
}

あとは親コンポーネント側で従来通り、子コンポーネントからのイベントをハンドリングすればOKです。

Vue 3からは.syncは廃止されているので、変わりにv-model:xxxを使用しています。

今回の例のようにv-model:countとして子コンポーネントに渡すと、親コンポーネント側で更新用の関数を用意しなくても子コンポーネントからupdate:countイベントが送出されてくれば、勝手に更新してくれます。

<template>
  <ChildComponent v-model:count="countRef" />
</template>

まとめ

  • setup関数の第1引数に渡されるpropsに親コンポーネントから渡されたデータが格納されている
  • setup関数の第1引数に渡されるpropsに分割代入を適用するとリアクティビティが失われる
  • setup関数の第2引数に渡されるcontextに用意されているemit関数で親コンポーネントにイベントを送出できる
  • setup関数の第2引数に渡されるcontextには分割代入を適用できる
  • Vue 3からは.syncが廃止されてv-model:xxxで同様のことが実現できる

watch

Options APIではwatchオプションを使用してリアクティブな変数を監視していましたが、Composition APIではwatch関数を使用します。

公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div>
    {{ count }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 1,
    };
  },

  watch: {
    count(count, prevCount) {
      console.log(count);
      console.log(prevCount);
    },
  },
};
</script>

Options APIではwatchオプションでリアクティブな変数を監視していました。

Composition API

Composition APIでは以下の手順でリアクティブな変数を監視します。

  1. setup関数内でwatch関数に監視対象とcallback関数を渡す
<template>
  {{ countRef }}
</template>

<script>
import { ref, watch } from "vue";

export default {
  setup() {
    const countRef = ref(1);

    // watch関数でリアクティブな変数を監視します
    watch(countRef, (count, prevCount) => {
      console.log(count);
      console.log(prevCount);
    });

    return {
      countRef,
    };
  },
};
</script>

<style>
</style>

この例では以下のようにリアクティブな変数が監視されます。
※実際にcountRefの値を変更する処理は用意していません

  1. countRefの値が変更される
  2. watchの第2引数に渡したcallback関数が呼ばれる
  3. count(変更後の値)とprevCount(変更前の値)が出力される
ロボット
watchの第1引数には監視対象、第2引数にはcallback関数を渡します。
callback関数の第1引数には変更後の値、第2引数には変更前の値が渡されます。
watch(
      /** 監視対象 **/
      /** callback関数 **/
    );

第1引数の監視対象には以下の関数で生成したリアクティブな変数を渡すことができます。

  • ref
  • reactive
  • computed

今回の例ではrefで生成したリアクティブな変数を第1引数に渡しています。

computedもrefと同じように動作しますが、reactiveの場合は全てのプロパティが監視対象となります。

ロボット
以下の例ではnameとobjが監視対象になります。
const obj = reactive({
  name: "",
  count: 1,
});
watch(
    obj /** nameとobjが監視対象になる **/,
    (obj, prevObj) => {
      console.log(obj);
    }
);

もし監視対象のプロパティを絞りたい場合には、以下のように変更したいプロパティを返す関数を第1引数に渡します。

ロボット
以下の例ではnameが監視対象になります。
const obj = reactive({
  name: "",
  count: 1,
});
watch(
  () => obj.name /** nameが監視対象になる **/,
  (name, prevName) => {
    console.log(name);
  }
);

またwatchは複数のリアクティブな変数を同時に監視することができます。

この場合は以下のように第1引数に配列を渡します。
第2引数のcallback関数にもそれぞれの変更前、変更後の値が渡されます。

const fooRef = ref(1);
const barRef = ref(1);

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  console.log(foo);
  console.log(bar);
  console.log(prevFoo);
  console.log(prevBar);
});
ロボット
複数を監視対象とした場合には、いずれかが変更された場合にcallback関数が呼ばれます。
またVue2と同じようにpropsをwatchで監視することもできます。
まずはpropsの全てのデータを監視する場合です。
この場合は以下のようにwatchの第1引数にpropsを直接渡します。

watch(
  props,
  (newProps, oldProps) => {
    console.log(newProps);
  }
);

もちろん以下のようにpropsの1つのプロパティに監視対象を限定することもできます。

watch(
  () => props.count,
  (count, prevCount) => {
    console.log(count);
  }
);

ここまでリアクティブな変数を監視する方法を見てきましたが、もちろん監視を停止することも可能です。

監視を停止したい場合にはwatchから停止用の関数が返されるので、その停止用関数を呼び出します。

const stop = watch(countRef, (count, prevCount) => {
  console.log(count);
});

// 監視を停止
stop();

まとめ

  • リアクティブな変数を監視するにはwatchを使用する
  • watchの第1引数には監視対象を渡す
  • 監視対象にはref・reactive・computedの戻り値を渡せる
  • 監視対象を絞りたい場合には監視対象を返す関数を渡す
  • watchの第2引数にはcallback関数を渡す
  • callback関数の第1引数には変更後の値が渡される
  • callback関数の第2引数には変更前の値が渡される
  • 配列を用いて複数の値を同時に監視できる
  • propsを監視するにはwatchの第1引数に渡す
  • propsの1つのプロパティを監視するにはwatchの第1引数に監視対象を返す関数を渡す
  • 監視を停止する場合はwatchから返された停止用関数を呼び出す

refs

Options APIではtemplate内の要素またはコンポーネントを参照する際にvm.$refsを使用していましたが、Composition APIではここまで使用してきたrefとvm.$refsが統合されています。

ロボット
Vue2で使っていたrefsは「template refs」、 Vue3のrefは「reactive refs」と呼ばれています。
公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div ref="root"></div>
</template>

<script>
export default {
  mounted() {
    console.log(this.$refs.root);
  },
};
</script>

Options APIでは$refsを使用してtemplate内でバインドした要素・コンポーネントを参照していました。

Composition API

Composition APIでは以下の手順で要素・コンポーネントを参照します。

  1. template内で「ref=」を用いてバインド
  2. setup関数内で同名のrefを定義
<template>
  <div ref="root"></div>
</template>

<script>
import { ref, onMounted } from "vue";

export default {
  setup() {
    const root = ref(null);

    onMounted(() => {
      console.log(root.value);
    });

    return {
      root,
    };
  },
};
</script>

この例では、divタグにref=”root”としています。

setup関数内でrefを用いて同名のrootを定義しているので、このrootにdivがバインドされます。

この対応するrefへの割り当てタイミングは初回レンダリング後なので、onMountedで初めてアクセスできるようになります。

  • バインドされたrefにアクセスできるのは初回レンダリング後

まとめ

  • Vue3では「template refs」と「reactive refs」が統合されている
  • template内でバインドしたrefと同名のrefをsetup関数内で定義する
  • バインドされたrefにアクセスできるのは初回レンダリング後

ライフサイクルフック

Options APIではcreatedやmountedオプションを使用してライフサイクルフックを定義していましたが、Composition APIではそれぞれのライフサイクルに対応したonXXX関数を使用します。

公式ドキュメントでは以下で説明されています。

Options API

<template>
  <div></div>
</template>

<script>
export default {
  created() {
    console.log("created");
  },

  mounted() {
    console.log("mounted");
  },
};
</script>

ここではcreated・mountedのみ定義していますが、Options APIではそれぞれのライフサイクルに対応したオプションを使用していました。

Composition API

Composition APIでは以下の手順でライフサイクルフックを定義します。

  1. setup関数内でそれぞれのライフサイクルに対応したonXXX関数を使用する
ロボット
beforeCreate・createdはsetup関数にまとめられています。
<template>
  <div></div>
</template>

<script>
import { onMounted } from "vue";

export default {
  setup() {
    console.log("created");

    onMounted(() => {
      console.log("mounted");
    });
  },
};
</script>

<style>
</style>

今回のコードではmountedに対応したonMountedを使用していますが、他のライフサイクルフックも同様に定義できます。

以下がOptions APIとComposition APIのライフサイクルフックの対応表です。

Options API Composition API
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
update onUpdate
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured

まとめ

  • それぞれのライフサイクルに対応したonXXX関数を使用する
  • beforeCreate・createdがsetupにまとめられている

まとめ

今回はComposition APIの基本的な使い方を見てきました。

ここまで読んでいただいた方はComposition APIの基本的な使い方を理解していただけたかと思います。

ロボット
確かに使い方は分かったけど、なんでOptions APIじゃなくてComposition APIを使うことが推奨されているんだろう。。。

冒頭でも少し触れましたが、Composition APIのメリットは可読性・再利用性の向上や型インターフェースの改善です。

このあたりに関しては以下の記事で解説していますので、気になる方は是非ご覧ください。

関連記事

2020年9月18日に「Vue.js v3.0」がリリースされ、その中でも特にVue Composition APIが注目されています。 Composition APIの使い方が分かったあとは、なぜ使うのかというところが気になりますよね。[…]

それでは、ここまでご覧いただきありがとうございました。