はじめに
Vue3で導入されたComposition API。使ってみて、Componentが簡潔になることを実感。あと、データごとに関心事を分けて書くことができ、スッキリかけて気持ちが良い。
一方、型付きにするにはコツがあるのでそれを書く。型推論が効いた状態って贅沢感があっていいです。
コード
reactiveを型付きにする
ジェネリックで型を教えると良い
const strRef = ref<string>('Hello');
const state = reactive<{ name: string, age: number }>({ name: 'taro', age: 10 });
reactiveだと冗長な感じがする。何より読みづらいから型部分は切り出すと良い感じ。
interface Person {
name: string,
age: number
}
とコンポーネントの頭当たりで定義して
const state = reactive<Person>({ name: 'taro', age: 10});
というような使い方をする。
定義部分が離れちゃうのは拡張する際にめんどくさいので、機能拡張が欲しい感じ。
型名を Person
ってしたけど、スコープがコンポーネント内に限られているので単純に State
でも良い気もする。(迷ってる。)
propsを型付きにする
Propsの型定義は特にComposition APIだからって変わらないですが、自分用のメモとして書く。
props {
person: Object as PropType<Person>,
persons: [] as PropType<Person[]>
}
配列やオブジェクトの場合、PropTypeを経由しないと型情報とみなされないようなので注意が必要。
(ComputedのときはRefになるんだっけ、調べなきゃ)
モジュール
Composition APIの良いところであるモジュール化なんだけど、実際使うときには注意が必要。
何かって言うと、 setup()
と同じように、生成にReactiveとComputedを定義する必要があるっていうこと。実行順を考えないとReactiveが動作しないという問題がある。
つまり、クラス定義を行う場合コンストラクタが肥大化するという問題がある。コンストラクタで行っても良いんだけど、インターフェイスに則った Factory method
を作ったほうが読みやすいように思える。
interface Person {
name: reactive<string>,
age: Ref<number>,
greet: Computed<string>
}
getPerson(name: string, age: number) {
const nameRef = ref<string>(name);
const ageRef = ref<number>(age);
const greet = Computed(() => `Hello ${nameRef.value}!`;
return { name: nameRef, age: ageRef, greet };
}
このように意外とスッキリ書ける。
メソッド内で関数書くことに抵抗がない言語なので、意外と Factory method
がクラスの定義と違いが少ない気がする。インターフェイスは冗長な感じになるけど。。。
サンプル
とりあえず、CodePenに(サンプル)を作った。
見どころとしては、person
を集約する persons
クラスを定義している点。Composition APIの良い点だと思うのだけど、reactiveを入れ子にすることができる。
interface Person {
name: Ref<string>,
age: Ref<number>,
greet: Computed<string>,
};
interface Persons {
persons: reactive<Person[]>,
add: (person: Person) => void,
remove: (person: Person) => void,
};
persons
はおおもとのコンポーネントである my-component
で利用され、 person
は person
コンポーネントで利用され、コンポーネントでなんの設定もせず、それぞれreactiveとして利用できる。
これのおかげで、コンポーネントは関数や変数のマッピングのみとなる。
app.component('person', {
template: // 省略
,
props: {
person: Object as PropType<Person>
},
setup(props, { emit }) {
const mode = ref<'view' | 'edit'>('view');
const remove = () => emit('remove', props.person);
const pushEdit = () => mode.value = 'edit';
const pushUpdate = () => mode.value = 'view';
return { remove, pushEdit, pushUpdate, mode };
}
});
この利点は、再利用というよりコンポーネントからロジックをモデルとして切り出すことによって見通しがよくなる点と、テストが容易なる利点がある。
やっててちょっと嫌になるのは配列の扱い。
const remove = (person: Person) => {
const index = persons.value.indexOf(person);
persons.value.splice(index, 1);
};
依存関係の更新を行うために、spliceで削除を行う必要がある。
ひと目で削除しているように見えないので、なにか機能拡張を用意したいなぁ。
終わりに
元々はVuexで型付でやろうとしていたのだけど、モジュール化がきれいにできないことが判ってきて、Coposition APIが依存関係がわかりやすいので試してみたら良かった。
また、モジュールの分割が容易なのは非常に助かった。
Typescriptとしては、奇妙な書き方になってしまうが、なれてしまえばなんの問題も無い。
結論としては、非常におすすめ!!
参考
ありがとうございます!
Vue Mastery: https://www.vuemastery.com/courses/vue-3-reactivity
先取りVue 3.x !! Composition API を試してみる
Vue.js の Composition API における親子コンポーネント間のデータ受け渡し
vue-composition-apiを使って機能単位のコード分割と合成を試してみた
Vue + TypeScriptでpropsのObjectやArrayに型をつける
Ref vs Reactive Vue3 Composition APIのリアクティブ関数の探究 / ref vs reactive Vue Composition API Deep in