2019/09/08 追記
現在 function apiはcloseして Composition APIが新たにRFCとなっています。
Composition APIについても以下にまとめています。
はじめに
最近のVue.jsの話題といえば、次期メジャーバージョンアップのVue3.xで採用が予定され、現在RFCとなっているvue-function-api ですね。親しみがある今のオブジェクトベースのAPIとはかなり違うので賛否両論あるようですが、実際に使ってみるとTypeScriptで書きやすく個人的には好みです。
この記事ではTypeScriptでのvue-function-apiの書き方を紹介します。(随時追加予定)
自分もまだまだ勉強段階なので間違ってる箇所あれば、気軽にコメントで指摘お願いします。
※ サンプルコードでは、明示的に型アノテーションつけていますが、実際には自動的に型推論される箇所が多いです。
※ Vue3.x では従来のオブジェクトベースのAPIはサポートされるので、バージョンアップの際に置き換えが必ず必要というわけではないです。
vue-function-apiの導入
vue-function-apiはpluginとして提供されているので、vue2.x でも使用可能です。
https://github.com/vuejs/vue-function-api
パッケージの追加
$ yarn add vue-function-api
pluginの設定
import Vue from 'vue'
import { plugin } from 'vue-function-api'
Vue.use(plugin)
以下にvue-function-api & typescript の練習用に作ったTodoアプリがあるので、実際の使い方はそちら参考にしてください。
https://github.com/kawamataryo/sandbox-vue-functional-api
componetの作成 - createComponent()
, setup()
vueのSFCの基本単位となるcomponentの作成は、createComponent<T>()
を使います。
そして慣れ親しんだvueのリアクティブなデータや、メソッド定義はcreateComponent<T>()
の引数オブジェクト内でsetup()
メソッドを定義して記述します。
また、template側で使う関数、変数はsetup()
内で変数に代入し、オブジェクトとしてreturnする必要があります。
従来
export default {
methods: {
greet: function() {
return "hello world"
}
}
}
vue-function-api(TypeScript)
export default createComponent({
setup() {
const greet: string = () => {
return "hello world";
};
return {
greet
};
}
});
型定義
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
props?: Props;
setup?: (this: undefined, props: {
[K in keyof Props]: Props[K];
}, context: Context) => object | null | undefined | void;
};
export declare function createComponent<Props>(compOpions: ComponentOptionsWithSetup<Props>): ComponentOptions<Vue>;
リアクティブなデータの保持 - value()
オブジェクトベースのAPIでのdata(){return {}}
に相当するのがvalue()
です。
vue-function-apiではオブジェクトでまとめず要素ごとに個別に定義します。そしてTypeScriptでは、Wrapper<T>
を使って型定義します。
また、template側で使う変数は関数と同様setup()
内で変数に代入し、オブジェクトとしてreturnする必要があります。
従来
export default {
data() {
return {
name: "taro",
address: {
postCode: 1234,
city: "tokyo"
}
};
}
};
vue-function-api(TypeScript)
type Address = {
postCode: number;
city: string;
};
export default createComponent({
setup() {
const name: Wrapper<string> = value("taro");
const address: Wrapper<Address> = value({
postCode: 1234,
city: "tokyo"
});
return {
name,
address
};
}
});
型定義
export declare function value<T>(value: T): Wrapper<T>;
算出プロパティ - computed
リアクティブな依存関係にもとづきキャッシュされる算出プロパティは、computed()
関数で定義します。
従来
export default {
data() {
return {
message: "Hello world";
}
},
computed: {
reversedMessage: function () {
return this.message.split("").reverse().join("")
}
}
}
vue-function-api(TypeScript)
export default createComponent({
setup() {
const message: Wrapper<string> = value("hello");
const reverseMessage: Wrapper<string> = computed(() => {
return message.value.split("").reverse().join("")
});
return {
message,
reverseMessage
};
}
});
型定義
export declare function computed<T>(getter: () => T, setter?: (x: T) => void): Wrapper<T>;
親から子への値の受け渡し - props
親から子コンポーネントに値を受け渡すprops
は、`createComponentのジェネリクスとしてporpsの型を渡して、引数オブジェクトのpropsのプロパティの値として文字列配列をanyにupキャストした上で型定義が必要です。
型定義見ると、ジェネリクスと Mapped typesを使っていて難解ですね。
従来
export default {
props: {
name: String,
age: Number
},
methods: {
userProfile: function {
return `名前: ${name}, 年齢: ${age}`
}
}
}
vue-function-api(TypeScript)
type Props = {
name: string;
age: string;
};
export default createComponent<Props>({
props: (["name", "age"] as any) as PropType<Props>,
setup(props) {
const userProfile = (): string => {
return `名前: ${props.name}, 年齢: ${props.age}`
}
return {
userProfile
};
}
});
型定義
export declare type PropType<T> = T;
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
props?: Props;
setup?: (this: undefined, props: {
[K in keyof Props]: Props[K];
}, context: Context) => object | null | undefined | void;
};
context関数 - emit
, refs
, slot
...
子から親へのデータ通信の際のイベント発火に使うemitなどのインスタンスメソッドは、setup()
メソッドが第2引数で受け取る、contextオブジェクトに定義されています。それを従来と同様の書き方で使えば大丈夫です。
例 emit
従来
export default {
methods: {
emitGreet: function() {
this.$emit('greet', 'Hello')
}
}
}
vue-function-api(TypeScript)
export default createComponent({
setup(props, context: Context) {
const emitGreet = () => {
context.emit("greet", 'Hello');
};
}
});
型定義
export interface Context {
readonly parent: Vue;
readonly root: Vue;
readonly refs: {
[key: string]: Vue | Element | Vue[] | Element[];
};
readonly slots: {
[key: string]: VNode[] | undefined;
};
readonly attrs: Record<string, string>;
emit(event: string, ...args: any[]): void;
}
変更の監視 - watch()
カスタムウォッチャのwatch()
の定義は、setup()
メソッド内のwatch
関数で定義します。
value側で適切に型定義をしていれば、特に型アノテーションは必要なく、型推論されます。
従来
export default {
data() {
return {
count: 0
}
},
watch: {
count: function (newVal, oldVal) {
if (newVal > oldVal) {
console.log("カウントアップ");
} else {
console.log("カウントダウン");
}
}
},
}
vue-function-api(TypeScript)
export default createComponent<Props>({
setup() {
const count: Wrapper<number> = () => value(0);
watch(count, (newVal, oldVal) => {
if (newVal.value > oldVal.value) {
console.log("カウントアップ");
} else {
console.log("カウントダウン");
}
});
return {
count
};
}
});
型定義
declare type watcherCallBack<T> = (newVal: T, oldVal: T) => void;
declare type watchedValue<T> = Wrapper<T> | (() => T);
declare type FlushMode = 'pre' | 'post' | 'sync';
interface WatcherOption {
lazy: boolean;
deep: boolean;
flush: FlushMode;
}
export declare function watch<T>(source: watchedValue<T>, cb: watcherCallBack<T>, options?: Partial<WatcherOption>): () => void;
export declare function watch<T>(source: Array<watchedValue<T>>, cb: watcherCallBack<T[]>, options?: Partial<WatcherOption>): () => void;
Lifecycle Hooks
vueのライフサイクルに合わせて処理を行いたい場合に使うライフサイクルフックは、setup()
メソッド内で関数として呼び出します。
処理はコールバックで渡します。
従来
export default {
created: function() {
// 何らかの処理
},
mouted: function() {
// 何らかの処理
},
beforeDestroy: funciton() {
// 何らかの処理
}
}
vue-function-api(TypeScript)
export default createComponent({
setup() {
onCreated(() => {
//何らかの処理}
})
onMounted(() => {
//何らかの処理}
})
onBeforeDestroy(() => {
//何らかの処理}
})
}
});
型定義
export declare const onCreated: (callback: Function) => void;
export declare const onBeforeMount: (callback: Function) => void;
export declare const onMounted: (callback: Function) => void;
export declare const onBeforeUpdate: (callback: Function) => void;
export declare const onUpdated: (callback: Function) => void;
export declare const onActivated: (callback: Function) => void;
export declare const onDeactivated: (callback: Function) => void;
export declare const onBeforeDestroy: (callback: Function) => void;
export declare const onDestroyed: (callback: Function) => void;
export declare const onErrorCaptured: (callback: Function) => void;
export declare const onUnmounted: (callback: Function) => void;