全集中 TypeScriptの呼吸 壱ノ型Mapped Types、弐の型Conditional Types、そして12の型のその先 type-fest

例えば

```typescript
class Dog {
  name: string
  age: number
  constructor (dogData: any) {
    this.name = dogData?.name
    this.age = dogData?.age
  }
  cry = () => {
    console.log(`I’m ${this.name}`)
  }
}
```

みたいなクラスがあった時に、constructorで受け取る型として

```typescript
{
  name: string
  age: number
}
```

みたいなのが欲しくなる事があります。筋力で解決するなら

```typescript
type DogData = {
  name: string
  age: number
}

const dataIsDogData = (data: unknown): data is DogData => {
  const d = data as DogData
  if (typeof d?.name !== 'string') return false
  if (typeof d?.age !== 'number') return false
  return true
} 

class Dog {
  name: string
  age: number
  constructor(data: unknown) {
    if (!dataIsDogData(data)) throw new Error('unknown data')
    this.name = data.name
    this.age = data.age
  }
  cry = () => {
    console.log(`I’m ${this.name}`)
  }
}
```

みたいになると思います。でもプロパティが10個の場合とかを想像するとちょっと筋肉が身震いしちゃいますよね。Proto3やGraphQL Queryを元にgenerateされているなら別に人力で書く必要のない型(IDLを書く手間はある)なんですが、[そういう手法が取れない事](https://firebase.google.com/docs/firestore?hl=ja)もあるかもしれません。

そんな時の選択肢の1つは[Utility Types](https://www.typescriptlang.org/docs/handbook/utility-types.html)を利用する事です。TypeScriptにはUtility Typesと呼ばれる便利な型がいくつか標準で搭載されています。例えばその中の1つである[Pick](https://www.typescriptlang.org/docs/handbook/utility-types.html#picktk)を使う事で

```typescript
type DogData = Pick<Dog, 'name' | 'age'>
```

といった風に書く事もできます。

でもプロパティ名を10個も羅列しないといけないのは、筋力に自信が無い人だとまだちょっと不安が残りますよね。犬だけじゃなく猫も増えるかもしれないし、10個のプロパティのうちnameだけが無くて代わりにserialNumberを持っているRobotの取り扱いを始める可能性だってあります。叶うならDogやCatやRobotからFunction型だけ取り除いた型を手に入れられないか。

そんな時の選択肢を広げてくれるのが[Mapped Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types)と[Conditional Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types)です。

```typescript
type DogData = {
  [Key in keyof Dog]: Dog[Key] extends Function ? never : Dog[Key]
}
```

制御構造みたいな見た目ですね。この中の

```typescript
{[Key in keyof Dog]: Hoge}
```
のfor in構文みたいな部分はMapped Types

```typescript
Dog[Key] extends Function ? never : Dog[Key]
```
の三項演算子みたいな部分はConditional Typesと呼ばれているものです。

# 壱ノ型Mapped Types

Mapped Typesはその型の各プロパティの型に対して何らかの操作を加えた新たな型を返します。(keyof Tを渡す使い方なら)

例えばUtility TypesのPickや[Readonly](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlyt)等もMapped Typesを利用して作られた型です。

```typescript
type ReadonlyDog1 = Readonly<Dog>
```

は、敢えてUtility Typesや型変数を使わずに書き直すなら

```typescript
type ReadonlyDog2 = {
    readonly [Key in keyof Dog]: Dog[Key]
}

type ReadonlyDog3 = {
    readonly [Key in 'name' | 'age' | 'cry']: Dog[Key]
}
```

のように書くこともできます。これは元となった型の各プロパティに対してreadonlyを与えた型を返しています。また必ずしも既存の型をベースにする必要もなくて

```typescript
type Meta = {
  [Key in 'title' | 'description'] : string
}
```

と書くのは

```typescript
type Meta = {
  title: string
  description: string
}
```

と書くのと等価です。

# 弐の型Conditional Types

三項演算子です。

```typescript
type Result = Pig extends Eatable ? Pork : Pet

type Eatable = {
  grade: number
}

type Pork = {
  grade: number
}

type Pet = {
  name: string
}
```

という型に対してもしもPigが

```typescript
type Pig = {
  grade: number
}
```

だった場合にはResultは

```typescript
type Result = {
  grade: number
}
```

になります。

# Mappded TypesとConditional Typesを組み合わせる

```typescript
type DogData = {
  [Key in keyof Dog]: Dog[Key] extends Function ? never : Dog[Key]
}
```

これらをまとめると「Dogの各プロパティ値の型に対してFunction型(に代入可能)ならnever型を、そうでなければそのプロパティ値の型をそのまま返す」型になります。never型というのは、触るとコンパイラに殺されるので触られていない事がコンパイラによって保証される型です。

そして、これをDog型とFunction型の組み合わせ以外でも使えるよう型変数TとCに置き換えると

```typescript
type FilterNot<T, C> = {
  [Key in keyof T]: T[Key] extends C ? never : T[Key]
}

type NonFunctionKeysOnly<T> = FilterNot<T, Function>

type DogData = NonFunctionKeysOnly<Dog>
type CatData = NonFunctionKeysOnly<Cat>
type RobotData = NonFunctionKeysOnly<Robot>
```

こうじゃ。

だいぶ虫様筋と外眼筋に優しくなりました。ここからneverを除いた型にしたかったり、さらに楽をしたければ、例えば[type-fest](https://github.com/sindresorhus/type-fest)みたいな型ライブラリを使うのもいいでしょう。例えばtype-festの[ConditionalExcept](https://github.com/sindresorhus/type-fest/blob/master/source/conditional-except.d.ts)を使うと

```typescript
import { ConditionalExcept } from 'type-fest'

type NonFunctionKeysOnly<T> = ConditionalExcept<T, Function>

type DogData = NonFunctionKeysOnly<Dog>
type CatData = NonFunctionKeysOnly<Cat>
type RobotData = NonFunctionKeysOnly<Robot>
```

と書く事ができます。この例に限らず、ある型を元に別の型を得たい場合や、Primitive型ほしくない？となった時などにはあると便利です。