TypeScript の Vue.extend(options) で options の型を明示せず data などの同時指定時に props の default がアロー関数でないと型の解釈ができなくなる
とりあえず試したのは VSCode, Vetur, nuxt-ts の環境です。
Vue.js での TypeScript 使用の前提
前提として・・・ Vue.js で TypeScript を使用する際にはnoImplicitThis
を true にしましょう。
this の推論が効くようになり data や props などの定義が見れるようになります。
{
"compilerOptions": {
"noImplicitThis": true
}
}
コンポーネントメソッド内で this の型をチェックするには strict: true (もしくは最低でも strict フラグの一部の noImplicitThis: true) を含める必要があることに注意してください。
props の推論の事例
options に型を指定しないケース
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
propId: {
type: Number,
default() {
return 1;
},
},
},
mounted() {
this.propId;
},
});
</script>
単純に propId
を定義しており、下記のようにその型が推論されています。
こちらに data を指定すると・・・
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
propId: {
type: Number,
default() {
return 1;
},
},
},
// こちらに data を指定
data() {
return {
name: 'Taro',
};
},
mounted() {
this.propId;
},
});
</script>
propId
が any
となりさらにエラー扱いになってしまいました。
一方で data に指定した name
は正しく解釈されています。
props の default をアロー関数にしてみる
これといって根拠がないんですが・・・いじっていたところ動いたケースとして
props の default の指定をアロー関数にしてみます。
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
propId: {
type: Number,
// 通常の関数のメソッド指定からアロー関数の指定に
// default() {
default: () => {
return 1;
},
},
},
data() {
return {
name: 'Taro',
};
},
mounted() {
this.propId;
},
});
</script>
先ほどでていたエラーは消え data と併用しつつ propId
が推論されています。
options に型を指定してみる
options の型に指定する ThisTypedComponentOptionsWithRecordProps
Vue.extend() の定義は node_modules/vue/types/vue.d.ts
にあり、
引数の指定の仕方によりオーバーロード?をして処理の流れを切り替えているようです。
export interface VueConstructor<V extends Vue = Vue> {
new <Data = object, Methods = object, Computed = object, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): CombinedVueInstance<V, Data, Methods, Computed, Record<PropNames, any>>;
// ideally, the return type should just contain Props, not Record<keyof Props, any>. But TS requires to have Base constructors with the same return type.
new <Data = object, Methods = object, Computed = object, Props = object>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): CombinedVueInstance<V, Data, Methods, Computed, Record<keyof Props, any>>;
new (options?: ComponentOptions<V>): CombinedVueInstance<V, object, object, object, Record<keyof object, any>>;
extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;
extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;
extend
の記述は 5 箇所のようですが・・・これまでの記述を踏襲するとすると
Props
が Object
となるような ThisTypedComponentOptionsWithRecordProps
が適当そうです。
ThisTypedComponentOptionsWithRecordProps
は node_modules/vue/types/options.d.ts
に定義があり・・・
/**
* This type should be used when an object mapped to `PropOptions` is used for a component's `props` value.
*/
export type ThisTypedComponentOptionsWithRecordProps
// この改行はないですみやすさのためです
<V extends Vue, Data, Methods, Computed, Props> =
object &
ComponentOptions<V, DataDef<Data, Props, V>, Methods, Computed, RecordPropsDefinition<Props>, Props> &
ThisType<CombinedVueInstance<V, Data, Methods, Computed, Readonly<Props>>>;
使用するための型定義として Vue
に加え、 Data
, Methods
, Computed
, Props
の interface
が必要そうです。
実際の型の指定事例
<script lang="ts">
import Vue from 'vue';
import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
interface Data {
name: string;
}
interface Methods {}
interface Computed {}
interface Props {
propId: number;
propAidei: string;
}
const options: ThisTypedComponentOptionsWithRecordProps<
Vue,
Data,
Methods,
Computed,
Props
> = {
props: {
propId: {
type: Number,
default() {
return 1;
},
},
propAidei: {
type: String,
default: () => {
return 'ichi';
},
},
},
data() {
return {
name: 'Jiro',
};
},
mounted() {
this.propId;
this.propAidei;
},
};
export default Vue.extend(options);
</script>
Methods
, Computed
については空の interface
にしています。
さらに props の指定に通常の関数、アロー関数それぞれを指定していますが・・・
エラーは発生せず props の推論も正しく行われています。
this の型
ちなみに this
の型はこんな感じになっています。
interface
でそれぞれ定義された Props
などが &
により Union Type Intersection Type になっているようです。
Intersection Types - Advanced Types - TypeScript
Union Types - Advanced Types - TypeScript
- Union Type は
|
によりつなげたものでした