VueJSをtypescriptでVue.extend
を使って記述していると、ちょいちょい、
Property 'XXX' does not exist on type 'CombinedVueInstance<Vue, ...>'
みたいなエラーに遭遇します。
これはわりと有名な問題で検索すると色々な情報がひっかかるんですが、「結局どうすればええんや?」というところがわかりにくかったので整理してみます。
調査自体は趣味の範疇ですが、調査に使ったコードは仕事のものなのでコードサンプルはありません。ごめんよ。
何が起こっているのか?
VueJSで画面(またはコンポーネント)を作る場合、独自にprops, state, methodなどを定義していくわけですが、その際に型推論が効かなくなって「そんなメソッド(プロパティ)知らんがな」と、言われているのがこのエラーです。
同じようにコードを書いていても型推論が効いたり聞かなかったりするのでやっかいです。
ちなみにこのエラーが出ても実行自体には問題ありません。
安直な解決方法
手っ取り早く解決するなら型を無視するのが楽です。
// @ts-ignore
this.someMethod()
@ts-ignore
を書くと次の行の型検査がスキップされます。
またはもっと安易にthis
をany
にキャストするという方法もあります。
(this as any).someMethod()
しかし、まぁ負けたような気がするのであまりお勧めではありません。
たまに有効な解決方法
検索をかけるとmethodやcomputedの定義で明示的に返り値の型を定義しましょう。という解をよく見かけます。
methods: {
someMethod(): string {
return "hoge"
},
}
これで型推論が有効になるケースもあるみたいですが、手元ではこれで解決したケースはありませんでした。。。
(ただし、既存のコンポーネントでエラーが発生していないものも多くあるので、そこではこれが効いているのかもしれません。)
いずれにせよ型の明示は人目での解析にも有益なので常につけるようにした方が良いです。
もうちょっと調査する
手元でエラーになるコンポーネントとならないコンポーネントのソースを見比べてますが、違いがよくわかりません。
何かしら型推論が効かなくなる条件があるはずですが、それが何なのかはよくわからないです。
これはVueJS本体の方でも長くオープンしている問題で半日やそこらの調査でどうにかなる種類のものではなさそうです。
そこでそちらは一旦あきらめてthis
の型を比べてみました。
vs-codeを使っていれば、マウスホバーでそこの型が明示されますがそこには明確に違いがありました。
エラーになる方
CombinedVueInstance<Vue, unknown, unknown, unknown, Readonly<Record<never, any>>>
ならない方
CombinedVueInstance<Vue, {
someData: number;
...
}, {
someMethod(): void;
...
}, {
...;
}, Readonly<...>>
エラーにならない方ではメソッド定義の内容が匿名型(? 呼び方がよくわからん)で、そのまま入ってますがエラーになる方ではunknown
となっています。
要するに型推論できてない、ってことですよね。(^^;
型を明示する
型が推論できないなら明示すればよいはずです。
VueJSのtypesを調べるとextend
はいくつかオーバーロードされていますが、ここで使われているのはおそらくこれ。
extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
Genericsで使われている型はそれぞれ以下を表しているようです。
- Data: stateの定義
- Methods: methodの定義
- Computed: computedの定義
- Props: propsの定義
ここに明示的な型定義を渡せば良さそうです。
こんな感じ
import Vue from 'vue'
import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options'
interface DataType {
someState: string
...
}
interface MethodType {
someMethod(): void
...
}
// 使ってない場合は空でOK
interface ComputedType {}
interface PropType {}
export default Vue.extend({
} as ThisTypedComponentOptionsWithRecordProps<Vue, DataType, MethodType, ComputedType, PropType>)
これでエラーは解消されます。
Vuexを使っていて、mapState
やmapActions
している場合はそこでインポートしたproperty/methodも定義に含める必要があります。
(ただ、エラーにならない方の定義を見るとmapActions
したメソッドは含まれていないので、そこは謎なんですが。。。)
まとめ
定義を二重で書くのは面倒かもしれませんが、言語によっては当たり前のことだったりするものもあるし個人的には先に定義だけ見られるのはWelcomeです。
VueJS自体での解決はどうなんでしょうかね。
クラスを使った書き方の場合に起こるかどうかは不明ですし、3系でTypeScriptサポートの強化があるので2系での解消はやる気無さそうな気がします。
いずれにせよ型を無視するよりは100倍マシなので、エラーに遭遇したときはこの方法で回避しますv