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

<img width="1280" alt="Advent Calnder.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16065/327f076d-33b6-a634-fbd6-9857c94967ee.png">

これは [bosyu Advent Calendar 2019](https://qiita.com/advent-calendar/2019/bosyu) の 4日目の記事です。
よろしくね。

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

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

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


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

```javascript:nazo.vue

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16065/6a99dbee-c1e5-73db-56f7-79f8edf675e9.png)

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

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

VSCode等でみると `state` は `string` となってしまいます :innocent: 


## なんでや

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

https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/reactive.ts#L129

```ts
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>` てのがきになりますね。
それを見てみましょう。

https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L17

```ts
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` の定義を見てみましょう。

https://github.com/vuejs/composition-api/blob/v0.3.4/src/reactivity/ref.ts#L9

```ts
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でなげるかとかですかね。

まあそんな感じ。

あとは邪悪だけど

```ts
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` は最凶(強)。