Composition API を触ってみた

## Composition API とは

[公式ドキュメント](https://vue-composition-api-rfc.netlify.app/)

コンポーネントの柔軟な合成を可能にする関数ベースのAPIです
Composition Function によりロジックの関心ごとにコードをまとめやすくなるのと、
型推論の改善も組み込まれているようです。

以前はコンポーネントでロジックを使いまわしたい時は `mixin` を使う必要がありましたが、namespace の衝突の問題がありました。 ( `this.~` )

今回はとりあえず Option API からの書き換えをメインに書きたいと思います。

## 開発環境

型推論 の機能もあるということで、 vue-cli で TypeScript 対応のプロジェクトを作成して試してみました
TypeScript 対応は[こちら](https://www.e-loop.jp/knowledges/16/)を参考にさせていただきました


```bash
$ vue create your-project-name
# オプションの class-style component は今回使わないので No にしました
```

Vue2系で Composition API を使うには `@vue/composition-api` が必要なのでインストールします

```bash
$ npm i @vue/composition-api
```

`main.ts` に 2 行追加します

```ts
import Vue from 'vue';
import App from './App.vue';
import VueCompositionApi from '@vue/composition-api'; // 追加

Vue.use(VueCompositionApi); // 追加
Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount('#app');

```

これで準備完了です！

## Options API からの書き換え

Options API は Vue の基本的な記述法です
テンプレート/スタイル部分のコードは省略します！

### props

- こちらは特に変化なしですね
- `setup` の第一引数で props を使用できます
- setup はこのあと出てきますが、初期化を行うような関数です

```ts
export default {
  props: {
    list: {
      type: Array,
      default: [],
    },
  },
  setup(props){
  }
}
```

### data

- `reactive` を使って、 `setup` の中に定義します
    - props のように例外もありますが、基本 setup の中に定義していく感じです
- setup で `state` を return します
- `state.{プロパティ名}` で参照できます

```ts
import { reactive } from '@vue/composition-api';

export default {
  setup() {
    const state = reactive({ 
      items: [],
      newItem: ''
    });
    return { state }
  }
}
```

ですが、 ↓ のように `toRefs` を使うのがおすすめみたいです
こうすることで、テンプレート部分で state を介さず直接プロパティ名で参照できるようになります

```ts
import { reactive, toRefs } from '@vue/composition-api';

export default {
  setup() {
    const state = reactive({ 
      items: [],
      newItem: ''
    });
    return { ...toRefs(state) }
  }
}
```

### computed

- `computed` を使います

```ts
import { reactive, toRefs, computed } from '@vue/composition-api';

export default {
  setup() {
    const state = reactive({ 
      items: [],
      newItem: ''
    });
    
    const totalCount = computed(() => {
      return state.items.length;
    });

    return { ...toRefs(state), totalCount }
  }
}
```

### methods

- 普通に関数を定義するだけです、 return もお忘れなく

```ts
import { reactive, toRefs } from '@vue/composition-api';

export default {
  setup() {
    const state = reactive({ 
      items: [],
      newItem: ''
    });

    const addItem = () => {
      state.items.push(state.newItem);
      newItem = '';
    };

    return { ...toRefs(state), addItem }
  }
}
```

### watch

- `watchEffect` を使います
- Option API の watch との違いは、 watchEffect の中で参照されているオブザーバブルな値が変更された場合に実行されます

```ts
import { reactive, toRefs, watchEffect } from '@vue/composition-api';

export default {
  setup() {
    const state = reactive({ 
      items: [],
      newItem: ''
    });

    watchEffect(() => {
      console.log(state.items);
    });

    return { ...toRefs(state) }
  }
}
```

### emit
- setup の第二引数 context を使って `context.emit` で実行
- キャメルケース使用不可。ケバブケースで記述する必要あり

```ts
export default {
  setup(props, context) {
    context.emit('value-change');
}
```

### ライフサイクルフック

- `on~` の形で使えます

```ts
import { onMounted } from '@vue/composition-api';

export default {
  setup() {
    onMounted(() => {
      console.log('mounted!');
    });
  }
}
```

## 型推論を効かせる

- TypeScript で書く場合、 `defineComponent` で setup に渡された引数の型推論を効かせることができるようになります
- props の型は `PropType` を使用します

例えばこんなコードの時

```ts
import { PropType, toRefs }

export default {
  props: {
    list: {
      type: Array as PropType<string[]>,
      default: [],
    },
  },
  setup(props) {
    const state = reactive<{ newItem: string }>({ newItem: '' });

    const addItem = () => {
      props.list.push(''); // <= 注目
      state.newItem = 0; // <= 注目
    };
    return { ...toRefs(state), addItem }
  }
}
```

まず `state.newItem` のほうは、 string 型に指定しているのでエラーになっています
型推論が効いていますね
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175234/2a8a2f9b-f339-e515-f47f-2b00f13f1f53.png" width="500">

`props.list` のほうは 型情報が不明になっていますね...
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175234/18bbf7d2-6b62-b951-29f5-0a342aeaf120.png" width="500">


これに `defineComponent` を使用します（全体を囲うだけ）

```ts
import { defineComponent, PropType, toRefs }

export default defineComponent({
  props: {
    list: {
      type: Array as PropType<string[]>,
      default: [],
    },
  },
  setup(props) {
    const state = reactive<{ newItem: string }>({ newItem: '' });

    const addItem = () => {
      props.list.push('');
      state.newItem = 0;
    };
    return { ...toRefs(state), addItem }
  }
})
```

お、型情報がきましたね！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/175234/79effc0e-ef04-fdca-ee69-50278176ddd2.png" width="500">


## マイグレーションについて

Vue2 から Vue3 への マイグレーションは [vue-codemod](https://github.com/vuejs/vue-codemod) を使えば簡単にできるとのことですが、Option API から Composition API へのマイグレーションは、少なくとも今のところは手動でやるしかないみたいですね...

## おわりに

今回は試せなかったのですが、 Composition Function も実装してみたいです。
おそらくここが Composition API の肝な気がする...

> Composition Functionとは、関連するロジックでまとめられ、カプセル化された関数のことです。Composition APIによってthisへの依存がなくなり、コードは関心ごとでまとめられるようになりました。まとめられた関数は純粋なJavaScript or TypeScriptの関数として抽出し、他のコンポーネントで再利用しやすくなります。
> https://techblog.zozo.com/entry/vue-options-api-to-composition-api#Composition-Function