2020年のVue.jsとReactの選定基準を考える（Hooks vs Composition API）

Vue.jsの次メジャーバージョン（v3）が2020年Q1にリリースされる。特に目新しいのが、**Vue Composition API**という、これまでのVueの書き方とは違う関数ベースのAPI。

これに伴って、Vue.jsとReactの選定基準についても改めて考えないとなぁと思い書いた。
今回はまず新しいVue Composition APIに触れて、最後にReactとの違いについて書いてみる。


## TL;DR（忙しい人向け）
- TypeScript前提ならReact
- TypeScriptを使わない（or 使えない）規模ならVue.js
- 既存の大規模Vue.jsプロジェクトは、徐々にComposition APIに移行すると幸せ

## Vue.js 3の Composition API とは
Composition APIでは、下のコードのように、`reactive`や`computed`といった関数を用いて組み立てていく。見て分かる通り、今までとは全く違う、React Hooksと近しい書き方になる。

```vue3-component.vue
<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const state = reactive({
      count: 0, // data: { count: 0 } と同じ
      double: computed(() => state.count * 2) // computed: { double: () => { this.count * 2 } } と同じ
    })

    // methods: { increments() { this.count++ } } と同じ (下でsetupからreturnしているため)
    function increment() {
      state.count++
    }

    // template内で使いたいものを返す
    return {
      state,
      increment
    }
  }
}
</script>
```

このComposition APIを触ってみた時は興奮したんだけど、冷静になって考えてみると、Vue.jsの存在意義ってなんだろうとふと思った。

## なぜVue.jsにComposition APIが実装されたか

Composition APIのRFCでも書かれているが、このAPIが誕生した理由は2つある。
https://vue-composition-api-rfc.netlify.com/#motivation

- ロジックの抽出と再利用性
- TypeScript（型推論）の改善

### ロジックの抽出と再利用性
VueはComponentの分割手法として、**Mixin**を提供していたが、Mixinでは2つの問題があった。

Mixinの1つ目の問題は**「Mixinにどのようなメソッドなどがあるかを、Mixinを利用する側から一見して分からない」**こと。

```vue2-component.js
export default {
  mixins: [myGreatMixin] // mixinが何を提供しているかはファイルを見ないと分からない
}
```

Mixinの2つ目の問題は、**「Mixinの中でしか用いないメソッドなどをprivateにすることができない」**こと。
[Vue.jsのスタイルガイド](https://jp.vuejs.org/v2/style-guide/#%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%81%AA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E5%90%8D-%E5%BF%85%E9%A0%88)では`$_mixinName_methodName`のようにprefix付きで命名することを強要している。

```vue2-component.js
const myGreatMixin = {
  methods: {
    publicMethod: function () {
      console.log('Hello from public')
    },
    $_myGreatMixin_privateMethod: function () { // 親からも呼べてしまう
      console.log('Hello from private')
    }
  }
}
```

これら2つの問題により、Vueが提供しているMixinは、可読性やメンテナンス性において良いものとは言えない。Mixinの中でさらにMixinを使う場合なんかは、正直書いていて楽しくない。

それに対し**Vue Compostion APIでは以下のように書くことができる**。

```vue3-component.js
export default createComopnent({
  setup() {
    const { hello } = useSayHello()

    onMounted(() => {
      hello() // 'Hello'
    })
  }
})

// lib/say-hello.js
export const useSayHello = () => {
  const hello = () => {
    console.log('Hello')
  }
  return { hello }
}
```

比較して分かる通り、Composition APIでは`const { hello } = useSayHello()`といった具合に、抽象化されたコードがどのようなメソッドやデータを提供しているかひと目で分かる。またプライベートなデータも`return`しない限りスコープを閉じることができる。

### 2. TypeScript（型推論）の改善
これは現状、Vue.jsとTypeScriptの相性が悪いという問題を抱えているためである。

例えば`Vue.extend()`内で、mixinが提供するメソッドなどに対して型推論する場合は、以下のように、そのmixinが提供するメソッドなどを全て個別に型定義しなければいけない。

```vue.d.ts
declare module 'vue/types/vue' {
  interface Vue {
    items: Item[] // 「itemsというdataを提供するmixin」のための定義
    setSnackbar: (message: string) => void // 「setSnackbarというメソッドを提供するmixin」のための定義
  }
}
```

これでは、Mixinとは別に型を二重定義する必要があり、本来バグを防ぐためのTypeScriptが逆にバグの温床になってしまう。**しかしComposition APIは純粋な関数**であるため、特に工夫をせずとも型推論ができる。

#### TypeScriptデコレータによる推論は廃止

一時期、Vueを[vue-property-decorator](https://github.com/kaorun343/vue-property-decorator)などのTypeScriptデコレータを使って型推論をするという流れがあり、Vue 3でもクラスベースのAPIを公式に提供しようかという話が挙がっていた。
しかしTypeScriptデコレータ自体まだexperimental（実験的）な機能であるため、その話は議論の末に捨てられてしまった。
詳細はIssueにある。[[Abandoned] Class API proposal by yyx990803 · vuejs/rfcs · GitHub](https://github.com/vuejs/rfcs/pull/17)

### Composition APIの登場

以上の諸問題によって、Vue.js は関数ベースのComposition APIを提供することを策定した。

Vue.js 2でもComposition APIを試せるようにプラグインを提供している。
https://github.com/vuejs/composition-api

## Vue.jsとReactはどちらを選ぶべきか

早速本題に入ってみる。

### TypeScriptならReact

まず、「はじめからTypeScriptで書く」というプロジェクトにおいては、Reactを選択するべきだと思う。Vue.jsであればComposition API自体がまず正式リリースされていないし、バージョン2の書き方は先に挙げたTypeScriptとの相性問題がある。それなら安定したReact Hooksを選ぶのが間違いない。

ではComposition APIが正式リリースされた際はどうすればいいか？。少し悩ましいがこれも自分の答えはReact。Composition APIから後発ならではの良さというのも特に感じなかったし、vuexやvue-routerといった周辺ツールに関しては、概念はReact系と一緒なのでそれほど学習コストに差はない。

またReactのコミュニティとの差を感じることもたまにある。例えばマテリアルデザインのUIライブラリにおいて、Vue.jsベースの[Vuetify.js](https://vuetifyjs.com/ja/)よりもReactベースの[Material-UI](https://material-ui.com/)がライブラリとして完成度が高い。他のライブラリのケースも、ほぼReactの方が安定して開発されている場面によく会う（2021年はそれほど変わらなくなると思うけど）。

### いつVue.jsを使うべきなのか

Vue.jsは、TypeScriptやReactの習熟度が高くないチームであったり、小規模なケースにおいて採用することで強みを発揮できる。

そもそもVue.jsは、親しみやすいインタフェースである程度の複雑なアプリケーションを作れる、といった点がユーザーに愛されていた点だと思っている。Mixinを使わなければVue.jsは読みやすく、それほどJavaScriptに精通していなくとも書くことができる。

またSSRアプリケーションのフレームワークとしてReactの[Next.js](https://nextjs.org/)よりも、Vue.jsの[Nuxt.js](https://ja.nuxtjs.org/)の方が予め用意してくれている領域が広く、サクッとモダンなアプリケーションを作ることができる。

そういった点でも、Vue.jsはReactよりも小規模なプロジェクトに向いている。

### いつComposition APIを使うべきか

Composition APIは、既にVue.jsを導入しているプロジェクトで有用な選択肢になる。

というのも、Vue2の記法自体は3でも使うことができ、Composition APIとも併用して動かすことができる。そのため、今現在運用しているVue 2のプロジェクトがTypeScriptを欲しくなるような規模になれば、部分的にComposition APIへ移行し、TypeScriptの強化とコードの抽象化を行うことができる。

## 所感
2020年の選定基準について、一通り書いてみた。

Vue.jsが型推論を考慮せずに設計されたこともあり、JavaScript界隈において少し曖昧な立ち位置になっているなぁと感じる。こういったVue.jsのAPIの拡張はメリットもある一方、ユーザーにとって選択疲れを引き起こしてしまっている。
もういっそのことTypeScriptを捨てて、バージョン2の書き方で貫き通してもよかったんじゃないかなぁと、少し思った。