521
366

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

先取りVue 3.x !! Composition API を試してみる

Last updated at Posted at 2019-09-08

Vue.js v3.xのRFC(Request for Comments) となっているComposition API を色々触ってみたのでまとめます。

Vue Composition API とは?

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

とある通り、コンポーネントのロジックの柔軟なコンポジションを可能にする関数ベースのAPIです。
型推論の改善と、合成関数によるロジックの整理が可能になっています。
@vue/composition-apiを追加することで、Vue2系でも使用することができます。

環境構築

vue-cli でプロジェクトを作成します。
ポイントはManually select featuresを選びTypeScriptを選択すること。class-componentは使わないので N を選んで下さい。

$ vue create practice-composition-api 
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter
? Use class-style component syntax? No # class-componentの利用では `N`を選んで下さい
...
$ cd practice-composition-api
$ yarn add @vue/composition-api 

続いて main.ts@vue/composition-api の利用を宣言します。

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

これでComposition APIを使う準備はOKです。

コンポーネントの作成 ~ defineComponent ~

defineComponent 関数によりコンポーネントを作成する事で、型推論が効くようになります。
また、今までのmethodsdataライフサイクルフックなどは全て、defineComponent に渡すオブジェクトのsetup() 関数内で宣言します。

sample.vue
<template>
...
</template>

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

export default defineComponent({
  setup() {
    // ここにリアクティブなデータ、関数を定義
  }
})
</script>

リアクティブなプロパティの宣言 ref, reactive

今までのdata()に相当するリアクティブなプロパティは、setup()内でref()またはreactive()で宣言します。
(正確には、reactivevue.observable()相当)
refは今回のRFCで新しく出た概念で、toRefsや、isRefなど新しいメソッドが追加されています。

<template> で使う値・関数は必ず、setup()のreturnでオブジェクトとして返す ことを忘れないで下さい。
今までと異なり冗長な気もしますが、スコープが狭まるので個人的には良いと思っています。

以下、messageOnemessageTwoはどちらもリアクティブな値です。
サンプルではreactive, refともに、ジェネリクスで型注釈加えていますが、記載しなくても型推論が効きます。

<template>
  <div>
    <h2>{{ state.messageOne }}</h2>
    <h2>{{ messageTwo }}</h2>
  </div>
</template>

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

export default defineComponent({
  setup() {
    const state = reactive<{ messageOne: string }>({
      messageOne: "Hello"
    });
    const messageTwo = ref<string>("こんにちは");

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

ここで気になるのが、reactive()と、ref()の使い分けですが、ドキュメントによるとどちらの動作も理解した上で使い分けることが良いと記載されています。

私自身まだ、あまり良く理解できてないので、また後で別記事でまとめたいです。

コンポーネント間での値の受け渡し props, emit

propsdefineComponent()に渡すオブジェクト内で、propsプロパティとして宣言します。
今までと同様に、propsに対してtypeや、default、requiredなどの制約も設定できます。
型推論を効かせたい場合は、別に型を定義して、setup()の引数としてpropsを渡す際に型アノテーションをつけます。


type Props = {
  message: string;
};

export default defineComponent({
  props: {
    message: {
      type: String,
      default: "default Value"
    }
  },
  setup(props: Props) {
    props.message //string型として型推論される
  }
})

emitsetup()に渡す第2引数contextのメソッドとして使用できます。
型定義は SetupContextです。他にも今まで this に対して呼んでいた slotparent, rootなども,contextから呼ぶことができます。

  setup(props: Props, context: SetupContext) {
    const emitSample = () => {
      context.emit("emit-sample", "サンプル");
    };
  }

以下、実際に propsemit を使ったサンプルです。
子コンポーネントのボタンで、親から受け取った値を変形し、emit で伝播させています。

ChildComponent.vue
<template>
  <div>
    <button @click="upperCaseMessage">To upper case</button>
  </div>
</template>

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

type Props = {
  message: string;
};

export default defineComponent({
  props: {
    message: {
      type: String,
      default: "default Value"
    }
  },
  setup(props: Props, context: SetupContext) {
    const upperCaseMessage = () => {
      context.emit("change-message", props.message.toUpperCase());
    };

    return {
      upperCaseMessage
    };
  }
});
</script>
ParentComponent.vue
<template>
  <div>
    <h1>{{ state.message }}</h1>
    <ChildComponent
      :message="state.message"
      @change-message="changeMessage"
    ></ChildComponent>
  </div>
</template>

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

export default defineComponent({
  components: {
    ChildComponent
  },
  setup() {
    const state = reactive({
      message: "Hello"
    });

    const changeMessage = (message: string) => {
      state.message = message;
    };

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

ライフサイクルフック

vueのライフサイクルに合わせて処理を実行するライフサイクルフックは、setup()で、onXXXの形式で設定できます。

import { onMounted, onUpdated, onUnmounted } from 'vue'

const MyComponent = {
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

以下前のインターフェイスとの対応表です。
created系は、setup関数に内包されたことに注意してください。

Vue 2.x Compostion API
beforeCreate -
created -
beforeMount onBeforeMount
beforeUpdate onBeforeUpdate
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

その他新たにonRenderTracked, onRenderTriggeredも追加されているようです。(まだ未確認)

おわりに

以上、Vue Composition APIの簡単な紹介でした。
デコレータを多用するClass Componentよりだいぶ分かりやすいインターフェースになった気がします。
defineComponentを使えば、型注釈ほぼ必要なく自然に型推論が効くのも良いです。

まだまだ、検討中ですが採用されて、より使いやすいVueになると良いですね。

参考

公式ドキュメント
分かりやすく情報がまとまっているのでオススメです。

521
366
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
521
366

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?