Madogiwa Blog

主に技術系の学習メモに使っていきます。

Vue.js: Composition APIを試してみたので使い方とかMEMO

Composition APIを色々触ってみたので使い方等をメモしておきます📝

Composition APIとは

Compostion APIとはVue.js 3.0から導入される予定のAPIです✨

Composition API: a set of additive, function-based APIs that allow flexible composition of component logic. https://composition-api.vuejs.org/#summary

概要は上記の通り、コンポーネントのロジックをfunctionベースの記載できるようなAPIとなっており、 公式ドキュメントに記載されたComposition APIが導入されたモチベーションは下記のようなものみたいです。

Logic Reuse & Code Organization
Better Type Inference
https://composition-api.vuejs.org/#summary

Composition APIを使うとコードの再利用とTypeScriptのサポートが受けやすくなるようです👀

※ちなみに従来の記載方法はOptions APIと呼ばれているようです。

次から実際の使い方を軽く見ていきます。

簡単な使い方

install

Vue.js 3はまだ正式にリリースされているわけではないので、今回はnpm packageとして切り出されたComposition APIを使っていきます📦

npm install --save-prod @vue/composition-api

有効化

Vue.useを使ってCompostion APIを有効化します⚡

import Vue from "vue";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);

Componentの定義

Componentを定義するためにはdefineComponentを使用します。

<template>
  <div />
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent } from "@vue/composition-api";
Vue.use(VueCompositionApi);

export default defineComponent({
  name: "Sample",
});
</script>
<style></style>

data

dataを使う場合はreactiveを使用します⚡ setup内でreactiveの引数にobjectを継承した方を持つ値を渡すことでリアクティブな属性を定義できます。

dataの型を定義するにはreactiveジェネリック型として任意の型を渡してあげます。

そしてsetupreturn { state }として値を返してあげます。

※従来のようにtemplate内で{{message}}と呼び出したい場合はreturn { ...toRefs(state) }としてあげます。toRefsをつけないとリアクティブでなくなってしまう💦

<template>
  <div>{{ state.message }}</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive } from "@vue/composition-api";
Vue.use(VueCompositionApi);

type State = { message: string };

export default defineComponent({
  name: "Sample",
  setup() {
    const state = reactive<State>({ message: "" });
    return { state };
  },
});
</script>
<style></style>

computed

computedを使う場合はcomputedを使用します⚡ setup内でcomputedの引数に関数を渡して上げることで定義することが出来ます。

そしてsetupreturn { state, strongMessage}としてcomputedで定義したものを合わせて返してあげます。

<template>
  <div>
    <p>{{ state.message }}</p>
    <p>{{ strongMessage }}</p>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);

type State = { message: string };

export default defineComponent({
  name: "Sample",
  setup() {
    const state = reactive<State>({ message: "" });
    const strongMessage = computed(() => state.message.toUpperCase());

    return {
      state,
      strongMessage,
    };
  },
});

methods

methodsは単純に関数を定義して、setupでreturnするオブジェクトに定義した関数を入れてあげます。

<template>
  <div>
    <p>{{ state.message }}</p>
    <p>{{ strongMessage }}</p>
    <p>{{ weakMessage(state.message) }}</p>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);

type State = { message: string };

export default defineComponent({
  name: "Sample",
  setup() {
    const state = reactive<State>({ message: "" });
    const strongMessage = computed(() => state.message.toUpperCase());
    const weakMessage = (message: string) => message.toLowerCase();

    return {
      state,
      strongMessage,
      weakMessage,
    };
  },
});
</script>
<style></style>

props

propsを使う場合は従来どおりpropsを定義したあとにsetupの引数を定義することで使用出来ます。 propsの型を定義するにはsetupの引数に型を定義してあげます。

<template>
  <div>
    <p>{{ state.message }}</p>
    <p>{{ strongMessage }}</p>
    <p>{{ weakMessage(state.message) }}</p>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed } from "@vue/composition-api";
Vue.use(VueCompositionApi);

type State = { message: string };
type Props = { initializeMessage: string };

export default defineComponent({
  name: "Sample",
  props: {
    initializeMessage: {
      type: String,
      default: "",
    },
  },
  setup(props: Props) {
    const state = reactive<State>({ message: "" });
    const strongMessage = computed(() => state.message.toUpperCase());
    const weakMessage = (message: string) => message.toLowerCase();

    return {
      state,
      strongMessage,
      weakMessage,
    };
  },
});
</script>
<style></style>

emit

emitを使うにはsetupの第2引数を定義して使用します。

あとはthis.$emitの代わりにcontext.emitを使用してあげます。

<template>
  <div>
    <p>{{ state.message }}</p>
    <p>{{ strongMessage }}</p>
    <p>{{ weakMessage(state.message) }}</p>
    <button @click="handleOnOK">
      OK
    </button>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, computed, SetupContext } from "@vue/composition-api";
Vue.use(VueCompositionApi);

type State = { message: string };
type Props = { initializeMessage: string };

export default defineComponent({
  name: "Sample",
  props: {
    initializeMessage: {
      type: String,
      default: "",
    },
  },
  setup(props: Props, context: SetupContext) {
    const state = reactive<State>({ message: "" });
    const strongMessage = computed(() => state.message.toUpperCase());
    const weakMessage = (message: string) => message.toLowerCase();
    const handleOnOK = () => {
      context.emit("ok");
    };

    return {
      state,
      strongMessage,
      weakMessage,
      handleOnOK,
    };
  },
});
</script>
<style></style>

Options APIからComposition APIへの書き換え

実際に適当なComponentを書き換えたみたものを一応のせておきます。

Options API

<template>
  <div class="ts-counter">
    <button @click="decrement(1)">
      -
    </button>
    {{ count }}
    <button @click="increment(1)">
      +
    </button>
  </div>
</template>
<script lang="ts">
import Vue from "vue";

interface Data {
  count: number;
}

export default Vue.extend({
  props: {
    initCount: {
      type: Number,
      default: 0,
    },
  },
  data(): Data {
    return {
      count: this.initCount,
    };
  },
  methods: {
    decrement(num: number) {
      this.count -= num;
    },
    increment(num: number) {
      this.count += num;
    },
  },
});
</script>
<style lang="scss" scoped>
.ts-counter {
  color: blue;
}
</style>

Composition API

<template>
  <div class="ts-counter">
    <button @click="decrement(1)">
      -
    </button>
    {{ count }}
    <button @click="increment(1)">
      +
    </button>
  </div>
</template>
<script lang="ts">
import Vue from "vue";
import VueCompositionApi, { defineComponent, reactive, toRefs } from "@vue/composition-api";
Vue.use(VueCompositionApi);

interface Data {
  count: number;
}

interface Props {
  initCount: number;
}

export default defineComponent({
  props: {
    initCount: {
      type: Number,
      default: 0,
    },
  },
  setup(props: Props) {
    const state = reactive<Data>({ count: props.initCount });
    const increment = (n: number) => {
      state.count += n;
    };
    const decrement = (n: number) => {
      state.count -= n;
    };
    return {
      ...toRefs(state),
      increment,
      decrement,
    };
  },
});
</script>
<style lang="scss" scoped>
.ts-counter {
  color: blue;
}
</style>

おわりに

Composition APIを使っていろいろやってみたのですが、最初のモチベーション部分のコードの再利用等はまだ大規模なフロントエンドを経験したことが無いので、ちょっとわからない部分も多かったのですが、タイプスクリプトのサポートの方はthisに依存するコードがなくなって非常に快適になったように感じました。

Option APIのほうがComponentのデータとロジックがプロパティで別れていてClassぽくて分かりやすいかなと個人的には思っていたのですが、Composition APIも書きやすいですね✨

参考資料

composition-api.vuejs.org

qiita.com

techblog.zozo.com