これは 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は最凶(強)。
