これは bosyu Advent Calendar 2019 の 4日目の記事です。
よろしくね。
bosyuのフロントはVueを使っているのですが、最近 Composition API が上がってきているのでそれを使うようにしていこうぜ〜〜〜
という感じでやっております。
https://github.com/vuejs/composition-api
これね。
でこれがたまにアレなところがあるのですが、先日おもしろいところを見つけたので、それについて書いていくぞというアレです。
多分近々直ります。
なにがおきた
タイトル通りなんですが、型が不思議になりました。
具体的なコードを書くと以下のようなときに型が?????となります。
export default createComponent({
setup () {
const state = reactive({
status: 'hoge',
value: 'fuga'
});
const v = state.value;
}
});
ここでは state
は以下のようになるように期待していますが、
type State = {
status: string;
value: string;
}
VSCode等でみると state
は string
となってしまいます
なんでや
ということで、どんなかんじに型定義されているかみてみましょ。
export function reactive<T = any>(obj: T): UnwrapRef<T> {
if (process.env.NODE_ENV !== 'production' && !obj) {
warn('"reactive()" is called without provide an "object".');
// @ts-ignore
return;
}
if (!isPlainObject(obj) || isReactive(obj) || isNonReactive(obj) || !Object.isExtensible(obj)) {
return obj as any;
}
const observed = observe(obj);
def(observed, ReactiveIdentifierKey, ReactiveIdentifier);
setupAccessControl(observed);
return observed as UnwrapRef<T>;
}
こんなかんじでした。
return
のところの型が as
になってて強さを感じる。
まあそれはさておきこの UnwrapRef<T>
てのがきになりますね。
それを見てみましょう。
export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRef2<V>
: T extends BailTypes
? T // bail out on types that shouldn’t be unwrapped
: T extends object ? { [K in keyof T]: UnwrapRef2<T[K]> } : T
// prettier-ignore
type UnwrapRef2<T> = T extends Ref<infer V>
? UnwrapRef3<V>
: T extends BailTypes
? T
: T extends object ? { [K in keyof T]: UnwrapRef3<T[K]> } : T
// ...
// prettier-ignore
type UnwrapRef10<T> = T extends Ref<infer V>
? V // stop recursion
: T
こんな感じになってます。
ConditionalTypes
をつかって型を判定していますね。
http://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types
でこの Ref
の定義を見てみましょう。
export interface Ref<T> {
value: T;
}
です。
つまり value
をメンバーとして持っている場合は Ref
を extends しているとみなされてしまい、
UnwrapRef2
UnwrapRef3
... と流れていってしまいます。
最終的に UnwrapRef10
にたどり着き V
となります。
結果的に最初に書いたように state
が string
のようになってしまうわけですね〜。
かいけつさく
https://github.com/vuejs/composition-api/pull/167
PRはでてますが、 Object
の中に Object
があったりすると、同様の問題が起きてしまうので困ったもんだなぁ〜という感じですね。
かんたんにはどうこうできるような感じではないので、できれば value
というのはできる限りつかわないようにするのがとりあえずの対策かなぁ〜。
またVue-nextでは同様の問題が起きないような対処がされているようです。
https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/ref.ts
なのでこれと同様な実装を composition-api
にPRでなげるかとかですかね。
まあそんな感じ。
あとは邪悪だけど
createComponent({
setup () {
const state = reactive({
status: 'hoge',
value: 'fuga'
}) as unknown as { status: string, value: string };
const v = state.value;
}
});
unknown
とか any
とか一回型をぶっ壊して、むりやりやっちゃうとかね。
最高にダメな感じだけど。
まとめ
- かたはむずかしい。
- かたはたのしい。
-
as unknow as Hoge
は最凶(強)。