LoginSignup
21
7

More than 3 years have passed since last update.

Vue Composition APIで型がぶっ壊れて楽しかったです

Posted at

Advent Calnder.png

これは bosyu Advent Calendar 2019 の 4日目の記事です。
よろしくね。

bosyuのフロントはVueを使っているのですが、最近 Composition API が上がってきているのでそれを使うようにしていこうぜ〜〜〜
という感じでやっております。

https://github.com/vuejs/composition-api
これね。

でこれがたまにアレなところがあるのですが、先日おもしろいところを見つけたので、それについて書いていくぞというアレです。
多分近々直ります。

なにがおきた

タイトル通りなんですが、型が不思議になりました。
具体的なコードを書くと以下のようなときに型が?????となります。

nazo.vue

export default createComponent({
  setup () {
    const state = reactive({
      status: 'hoge',
      value: 'fuga'
    });
    const v = state.value;
  }
});

image.png

ここでは state は以下のようになるように期待していますが、

type State = {
  status: string;
  value: string;
}

VSCode等でみると statestring となってしまいます :innocent:

なんでや

ということで、どんなかんじに型定義されているかみてみましょ。

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 となります。

結果的に最初に書いたように statestring のようになってしまうわけですね〜。

かいけつさく

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 は最凶(強)。
21
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
7