グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



過去のコードをComposition APIを使って書き直してみる(前編)

VueのメジャーバージョンアップがQ3 2020に予定されており、Roadmap通りであればもうすぐ公開されます。
そこで、Vue3の目玉のひとつである「Composition API」を使って、過去に当ブログでご紹介したコードを書き直してみたいと思います。

今回は前編として、Composition APIについてまとめて行きます。

Composition APIとは?

Introducing the Composition API: a set of additive, function-based APIs that allow flexible composition of component logic.

公式には「コンポーネントロジックの柔軟な構成を可能にする追加の関数ベースのAPI」と記載があります。

具体的には、コードが見やすくなったり、コードの共通化がしやすくなったり、TypeScriptライクになったりするみたいです。

筆者の環境

$ vue -V
@vue/cli 4.4.1

環境構築

  • presetは「Manually select features」を選択します
  • featuresは今回「TypeScript」を指定します。
  • Use class-style componentは「No」を選択します
$ vue create composition-api
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
? Save this as a preset for future projects? No
  • Vue2系でComposition APIを使えるよう、「@vue/composition-api」をAddします
$ cd composition-api
$ yarn add @vue/composition-api
  • main.tsでComposition APIを宣言します
import Vue from 'vue'
import VueCompositionApi from "@vue/composition-api";

Vue.use(VueCompositionApi);

以上で、Vue2系でもComposition APIが使えるvue-cliの環境ができました。

書き方の違い

標準(Option API)のTypeScriptとComposition APIを使用した書き方の違いを見ていきます。

定義

Option API

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
});
</script>

Composition API

 setup関数 の中にdataやmethodを定義していきます。
 defineComponent を用いることで型推論を有効化しています。

<script lang="ts">
import { defineComponent } from "@vue/composition-api";

export default defineComponent({
    setup() {
    }
});
</script>

data

Option API

<script lang="ts">
...
export default Vue.extend({
  data: () => {
    return {
      title: "option-api",
      state: {
        count: 0
      }
    }
  }
});
</script>

Composition API

 ref関数  reactive関数 を用いて宣言します。
 <template> 内で扱うdataはsetup関数内でreturnしてあげます。

Use ref and reactive just like how you’d declare primitive type variables and object variables in normal JavaScript. It is recommended to use a type system with IDE support when using this style.

とあるように、 ref はプリミティブな値、 reactive はオブジェクトを宣言するときに使いますが、まだ明確な使い分けは決まっていないようです。

<script lang="ts">
import { defineComponent, ref, reactive } from "@vue/composition-api";

export default defineComponent({
  setup() {
    // data
    const title = ref<string>("composition-api");
    const state = reactive<{ count: number }>({
      count: 0
    });

    return {
      title,
      state
    }
  }
});
</script>

method

Option API

<script lang="ts">
...
export default Vue.extend({
  ...
  methods: {
    increment(count: number): void {
      this.state.count = count;
    }
  }
});
</script>

Composition API

methodもdataと同様にsetup関数内で定義し、returnします。

<script lang="ts">
...
export default defineComponent({
  setup() {
    ...
    const state = reactive<{ count: number }>({
      count: 0
    });

    // method
    const increment = (count: number): void => {
      state.count = count;
    };

    return {
      state,
      onIncrement
    }
  }
});
</script>

computed

Option API

<script lang="ts">
...
export default Vue.extend({
  ...
  computed: {
    double(): number {
      return this.state.count * 2;
    }
  }
});
</script>

Composition API

computedはsetup関数内で computed 関数を用いて定義します。
こちらもdata, methodと同様、returnしてあげます。

<script lang="ts">
import { defineComponent, reactive, computed } from "@vue/composition-api";
export default defineComponent({
  setup() {
    const state = reactive<{ count: number }>({
      count: 0
    });

    // computed
    const double = computed((): number => state.count * 2);

    return {
      state,
      double
    }
  }
});
</script>

props, emit

Option API

// Parent.vue
<template>
  <div>
    <child
      :count="state.count"
      @increment="onIncrement"
    />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import Child from "@/components/Child.vue";

export default Vue.extend({
  components: {
    Child
  },

  data: () => {
    return {
      state: {
        count: 0
      }
    }
  },

  methods: {
    onIncrement(payload: number): void {
      this.state.count = payload;
    }
  }
});
</script>
// Child.vue
<template>
  <div>
    <button @click="increment">
      {{ count }}
    </button>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  props: {
    count: {
      type: Number
    }
  },

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

Composition API

親ComponentはOption APIと基本的には同じです。
v-bindで子Componentにpropsでデータを渡し、子ComponentのEmit Eventを受けて発火させたmethodで受け取ったデータを処理します。

// Parent.vue
<template>
  <div>
    <!-- 
      v-bindで子Componentにデータを渡し、
      Emit Eventでmethodを発火させる。
     -->
    <child
      :count="state.count"
      @increment="onIncrement"
    />
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive } from "@vue/composition-api";
import Child from "@/components/Child.vue";

export default defineComponent({
  components: {
    Child
  },
  setup() {
    const state = reactive<{ count: number }>({
      count: 0
    });

    // 子Componentから受け取ったデータを処理する
    const onIncrement = (payload: number): void => {
      state.count = payload;
    };

    return {
      state,
      onIncrement
    }
  }
});
</script>

子Componentではpropsで受け取った値をsetup関数に渡します。
emitには this ではなく context を用います。
contextの型は SetupContext です。

// Child.vue
<template>
  <div>
    <!-- Click Eventでmethodを発火 -->
    <button @click="increment">
      {{ count }}
    </button>
  </div>
</template>

<script lang="ts">
import { defineComponent, SetupContext } from "@vue/composition-api";

// Propsの型定義
type Props = {
  count: number;
};

export default defineComponent({
  props: {
    count: {
      type: Number
    }
  },

  // setup関数にpropsとcontextを渡す
  setup(props: Props, context: SetupContext) {
    // Emit Eventで親Componentのmethodを発火させる
    const increment = () => {
      context.emit("increment", props.count + 1);
    };

    return {
      increment
    };
  }
});
</script>

Lifecycle Hooks

Option API

<script lang="ts">
...
export default Vue.extend({
  ...
  mounted() {
    console.log("mounted!");
  },
  updated() {
    console.log("updated!");
  },
  destroyed() {
    console.log("destroyed!");
  }
});
</script>

Composition API

setup関数内で各関数を定義し、その中にロジックを書きます。
マッピングは下記の表の様になっています。
 created  beforeCreate はsetup関数に内包されています。

Option API Composition API
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
activated onActivated
deactivated onDeactivated
errorCaptured onErrorCaptured
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
import { onMounted, onUpdated, onUnmounted } from "@vue/composition-api";

export default defineComponent({
  setup() {
    onMounted(() => {
      console.log("mounted!");
    });
    onUpdated(() => {
      console.log("updated!");
    });
    onUnmounted(() => {
      console.log("destroyed!");
    });
  }
});
</script>

まとめ

筆者自身がフロントエンド開発経験が少ないこともありますが、 this に縛られ、思うようにmethodを切り出せなかったり、TypeScriptの型推論と格闘したりと苦い思い出があります。 今回Composition APIを導入してみて、ストレスフリーな書き味になったなと感じました。

次回は、当ブログの過去記事「JavaScriptフレームワークの「Vue.js」を使ってToDoリストを実装してみよう」前編 後編 のコードを実際にComposition APIで書き直しながらリファクタリングしていきたいと思います。

この記事を書いた人

ばね
ばねソリューション事業部 システムエンジニア
東京で2年半エンジニアとしての経験を積み、浜松にUターンの後、アーティスへ入社。
ソリューション事業部のWebエンジニアとして、システムの設計・開発・保守・運用からインフラまで幅広く従事している。
フルスタックエンジニア目指して現在も勉強の日々。車が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします