皆さんこんにちは虎の穴ラボのY.Fです。
最近のWebフロントエンド界隈の話題を追っていると、フレームワークの勢力図的にはReactとVueの2大巨塔という感じがします。 特にVueに関してはメジャーバージョンアップであるVue3のリリースが迫っている状況です。
今回のブログ記事ではVue3で導入され、既存のVueコンポーネントの書き方をがらっと変える可能性がある composition API
について書いてみようと思います。
Vue3について
Vue3は2020年の第1クォーターにリリースが予定されている、Vue.jsのメジャーバージョンアップです。以下のような追加、変更が見込まれています。
- コードベースをTypeScriptに
composition api
の追加- パフォーマンスの向上
- Time Slicingをサポート
- slotsの扱いを変更
- etc...
現在はα版がリリース済みです。Vuexなどに対する対応はβ版で予定されています。
準備自体も着々と進められており、以下のようなブログ投稿やリポジトリが用意されていたりします。
全体の概要がまとめられているブログ記事 madewithvuejs.com
リポジトリ github.com
vue-cliのプラグイン github.com
composition api
について
composition api
はコンポーネントを構築する新しい方法です。語弊を恐れずに言うと、ReactのHooksに近い機能となります。
Vue2ではコンポーネントを構築する際に以下のような問題がありました。
- TypeScriptとの相性が悪い
- アプリケーションが巨大になるとコードの把握が出来ない
- コンポーネント間でのロジック再利用が難しい
composition api
は上記の問題を解消するために新たにVueに追加されるAPIとなります。詳細に関してはRFC用のサイトがあるので見てみてください。
vue-composition-api-rfc.netlify.com
まだVue3はリリースされていませんが、Vue2で先取りできるようにプラグインが用意されています。 github.com
今回はこのプラグインを使ってサンプルアプリを作りつつ、新旧の書き方を比較して見たいと思います。
作るもの
以下の条件でよくあるTODOアプリを作ります。
- データ永続化は無し
- TypeScriptを利用
- Vue2版は
class component
を利用しない
Vue2までの書き方
Vue2までの書き方で素直に実装すると以下のような形になると思います。
<template> <div> <div> <input type="text" v-model="inputTodo" /> <button type="button" @click="submit">追加</button> </div> <div> TODO一覧: <p v-for="(todo, idx) in todos" :key="idx"> <span>{{ todo }}</span> </p> </div> </div> </template> <script lang="ts"> import Vue from "vue"; export default Vue.extend({ name: "TodoList", data() { return { todos: [] as string[], inputTodo: "" }; }, methods: { submit() { if (this.inputTodo !== "") { this.todos.push(this.inputTodo); this.inputTodo = ""; } } } }); </script>
この形式でも良いのですが、以下のような問題があります。
- 個々のメソッドやプロパティの役割がわかりにくい
- TypeScriptを使っている時にthisに対する推論が効きにくい
- 型推論の力を発揮するには、Vue.extendを利用しなければ行けないなど工夫が必要になる
2個目の問題についてはvue-class-componentを利用することで解消出来ますが、今度はデコレータがたくさん必要になってしまいます。
では次にVue3のcomposition apiを使った方法を見てみます。
Vue3のcomposition apiを使った書き方
先述したライブラリを使います。
<template> <div> <div> <input type="text" v-model="state.inputTodo" /> <button type="button" @click="submit">追加</button> </div> <div> TODO一覧: <p v-for="(todo, idx) in state.todos" :key="idx"> <span>{{ todo }}</span> </p> </div> </div> </template> <script lang="ts"> import { createComponent, reactive } from "@vue/composition-api"; interface TodoState { todos: string[]; inputTodo: string; } export default createComponent({ name: "TodoList", setup() { const state = reactive<TodoState>({ todos: [] as string[], inputTodo: "" }); const submit = () => { state.todos.push(inputTodo); inputTodo = ""; }; return { state, submit }; } </script>
名前から役割が想像しづらいdataメソッドやmethodプロパティがなくなって、大分スッキリしたように見えるのではないでしょうか?
- createComponent関数を使ってコンポーネントを作成
- setup関数で各種設定を行う
- setup関数からreturnされるものがtemplateの中で利用できる
- setup関数はただの関数なので、TypeScriptによる補完が効きやすい
肝はsetup関数になります。今まで dataメソッドやmethodプロパティで定義されていた変数や関数はこのsetup関数の中で定義していくことになります。
setup関数はあくまで関数です。したがって、中身の処理は別ファイルに切り出すことも出来ます。
(logic.ts)
import { reactive } from "@vue/composition-api"; interface TodoState { todos: string[]; inputTodo: string; } const useTodoState = () => { const state = reactive<TodoState>({ todos: [] as string[], inputTodo: "" }); const submit = () => { state.todos.push(state.inputTodo); state.inputTodo = ""; }; return { state, submit }; }; export default useTodoState;
(TodoList.vue)
<template> <div> <div> <input type="text" v-model="state.inputTodo" /> <button type="button" @click="submit">追加</button> </div> <div> TODO一覧: <p v-for="(todo, idx) in state.todos" :key="idx"> <span>{{ todo }}</span> </p> </div> </div> </template> <script lang="ts"> import { createComponent } from "@vue/composition-api"; import useTodoState from "@/logics/logic"; export default createComponent({ name: "TodoList", setup() { const { state, submit } = useTodoState(); return { state, submit }; } }); </script>
変数の監視
setup関数の中で見慣れないreactiveメソッドが使われていると思います。Vue2でのdataに値するものになります。(実際はVue.observable()) reactiveで定義された変数が変更されると自動で表示も変更されます。
Vue2触ったことある方は、computedやwatchはどこにいったんだ?と思うかもしれません。これらもcomposition apiの関数として提供されます。
watchも似たような形なので今回はcomputed関数のみ紹介します。
<template> <div> <div> <input type="text" v-model="state.inputTodo" /> <button type="button" @click="submit">追加</button> </div> <div> <!-- 追加 --> TODO個数: {{ todoSum }} </div> <div> TODO一覧: <p v-for="(todo, idx) in state.todos" :key="idx"> <span>{{ todo }}</span> </p> </div> </div> </template> <script lang="ts"> import {computed, createComponent} from "@vue/composition-api"; import useTodoState from "@/logics/logic"; export default createComponent({ name: "TodoList", setup() { const { state, submit } = useTodoState(); // 追加 const todoSum = computed(() => state.todos.length); return { state, todoSum, submit }; } }); </script>
これらも当然別ファイルなどに切り出すことが可能です。
ライフサイクルフック
ライフサイクルフックも同様にsetup関数の中で定義することが可能です。
export default createComponent({ name: "TodoList", setup() { const { state, submit } = useTodoState(); const todoSum = computed(() => state.todos.length); // ライフサイクルフックの定義 onMounted(() => { console.log("mounted todo list"); }); return { state, todoSum, submit }; } });
特に説明不要かと思います。ライフサイクルフックに関しては特にtemplateでは使われないのでreturnは不要になっています。
まとめ
簡単にですがVue3で導入される目玉機能の一つであるcomposition apiについて紹介しました。
今回は以下については触れませんでした。
- composition apiとVuexを一緒に使う方法
- jsx(tsx)対応について
2つ目に関してはcomposition apiが入っても課題となる、template内でのTypeScript対応の解決策となる可能性があるので、今後も楽しみにしておきたいと思います。
P.S.
なんと、大阪説明会に続き札幌で説明会を実施することになりました!近隣にお住みの方はぜひお申し込みください!
また、今年もLT会を順次開催予定です。
他にもカジュアル面談は随時受付中ですので、気になる方はぜひお申し込みください