flowtypeのclassとobject typeについて整理する

flowのclass/object型の対応が独特でハマったのでメモ。

# 前提 - nominalとstructuralな型付け
一般的な型システムには以下の二種類が存在する。
### nominal typing
明示的にclass/interfaceを宣言し、それを元に型を判定する。
java/C#などがコレ。
### structural typing
objectのキーに注目し、それが一致するかで型を判定する。
typescript、golangなどがコレ(らしい)。

flowの型システムには、その二種類の型付けが混在している。

# Class - nominal typing
Classはnominalな型付けで判定される。

```js
class User {
  id: string
}
class GuestUser extends User {
  id: string
  expire: string
}
class AdminUser {
  id: string
  password: string
}

const guest :User = new GuestUser() // work
const admin :User = new AdminUser() // error!  
```
java/C#などを触っていれば、特に違和感無いと思います。

# Object type - structural typing
typeで宣言されるobject typeは、structuralな判定が行われる。

```javascript
type UserParam = {
  id: string
}
type GuestUserParam = {
  id: string,
  expire: string
}
type AdminUserParam = {
  id: string,
  password: string
}

const guest :GuestUserParam = {
    id: 'id-1',
    expire: '2018-06-06T14:00:05.182Z'
}
const admin :AdminUserParam = {
    id: 'id-2',
    password: 'passw0rd!'
}

const userA :UserParam = guest　// work
const userB :UserParam = admin  // work
```
これもflowの資料みていると違和感ありませんね。

# Object type と class
この二つが混ざるとちょっと妙な動きをします。

```javascript
class AdminUser {
  id: string
  password: string
}

type AdminUserParam = {
  id: string,
  password: string
}

const admin :AdminUserParam = {
    id: 'id-2',
    password: 'passw0rd!'
}

const userA :AdminUser = admin // error!
```
classで宣言された型は「インスタンスがそのクラスorサブクラスによって生成された型か」を判定するため、フィールドの値が同じでも型判定が失敗します。


```javascript
class AdminUser {
  id: string
  password: string
  constructor(params){
    this.id = params.id
    this.password = params.password
  }
}

const userA :AdminUser = new AdminUser({id: '',
 password: ''})
const userB :AdminUser = Object.assign({},userA,{id: 'test'})　// error!
const userC :AdminUser = new AdminUser(Object.assign({},userA,{id: 'test'})) //work!
```
userBのような、Object.assignによるオブジェクトの更新も、classから生成されていないオブジェクトなのでエラーが出ます。  
コレは結構紛らわしいですね。
userCのようにconstructorにもう一度つっこむと正常にパースされて型がつきます。オブジェクトスプレッドと合わせて`new AdminUser({...userA,id: 'test'})`とか書くと見た目も良いかと思われます。  
この特徴から、クラスを作ると以下のような運用になることが多いです。

```AdminUser.js
export default class AdminUser {
  id: string
  password: string

  constructor(params: AdminUserParam){
    this.id = params.id
    this.password = params.password
  }
}

export type AdminUserParam = {
  id: string,
  password: string
}
```

ちょっと冗長ですね。もっといいやり方あったら教えてください。

# ちなみに
typescriptはclassのインスタンスもstructuralに型付けがされるため、以下のようになります。

```typescript
class AdminUser {
  id: string
  password: string
  constructor(params){
    this.id = params.id
    this.password = params.password
  }
}

const userA :AdminUser = new AdminUser({id: '',
 password: ''})
const userB :AdminUser = Object.assign({}, userA, {id: 'test'})　// work!
const userC :AdminUser = {id: '', password: ''} //work
```

上のようなややこしさが無くシンプルですね。
ただ、コレはコレでピュアオブジェクトとクラスインスタンスを型で区別できないという問題があったりします。一長一短ですね。

# 追記
@kinzal 様より、以下のようにすると重複なしでかけるとのコメントをいただきました！


```AdminUser.js
export default class AdminUser {
  id: string
  password: string

  constructor(params: AdminUserParam){
    this.id = params.id
    this.password = params.password
  }
}

export type AdminUserParam = $Diff<AdminUser, {}>
```

ただ、これを使って集約したクラスを表現したところ、また悩みにブチ当たったので記事書きました。
[次の記事はこちら](https://qiita.com/KONDO-Yuuki/items/b8449779a03c35d634a2)