🥳

2021/06/14に公開

# TL;DR

``````type KeysMatching<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}[keyof T]
``````

または

``````type KeysMatching<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: any
} &
keyof T
``````

👇

``````type Example = { foo: number; bar?: string; baz?: number }
type T1 = KeysMatching<Example, number> // 'foo' | 'baz'
type T2 = KeysMatching<Example, string> // 'bar'
``````

# 解説

stack overflow にも解説があるので蛇足とはなりますが、一応解説をします。

## `keyof`

``````type Point = { x: number; y: number }
type P = keyof Point // 'x' | 'y'
``````

## Indexed Access Types (Lookup Types)

``````type Person = { age: number; name: string; alive: boolean }
type I1 = Person['age' | 'name'] // string | number
type I2 = Person[keyof Person] // string | number | boolean
``````

## `in`

``````type Fish = { swim: () => void }
type Bird = { fly: () => void }

const move = (pet: Fish | Bird): void => {
if ('swim' in pet) {
return pet.swim() // pet = Fish
}
return pet.fly() // pet = Bird
}
``````

※ Mapped Types の `in` とは区別して考えた方がよさそう

## Mapped Types

`Partial<T>` といった組み込みのユーティリティタイプは以下を参照のこと

### `{ [ P in K ] : T }`

``````type T1 = { [P in 'x' | 'y']: number } // { x: number; y: number }
type T2 = { [P in 'x' | 'y']?: number } // { x?: number; y?: number }
type T3 = { readonly [P in 'x' | 'y']: number } // { readonly x: number; readonly y: number }

// type Record<K extends keyof any, T> = { [P in K]: T }
type T4 = Record<'x' | 'y', number> // { x: number; y: number }
``````

### `{ [ P in K ] : T[P] }`

``````type Point = { x: number; y: number; z: number }
type T1 = { [P in 'x' | 'y']: Point[P] } // { x: number; y: number }

// type Pick<T, K extends keyof T> = { [P in K]: T[P] }
type T2 = Pick<Point, 'x' | 'y'> // { x: number; y: number }
``````

### `{ [ P in keyof T ] : T }`

``````type Point = { x: number; y: number }
type T1 = { [P in keyof Point]: number } // { x: number; y: number }

type T21 = { [P in keyof Point]?: number } // { x?: number; y?: number }
type T22 = { [P in keyof Partial<Point>]: number } // { x?: number; y?: number }
type T23 = { [P in keyof Partial<Point>]-?: number } // { x: number; y: number }, -? で ? (optional) を無効にできる

type T31 = { readonly [P in keyof Point]: number } // { readonly x: number; readonly y: number }
type T32 = { [P in keyof Readonly<Point>]: number } // { x: number; y: number }, readonly にはならない
``````

### `{ [ P in keyof T ] : T[P] }`

``````type E1 = { foo: string; bar: string }
type T2 = { [P in keyof E1]: E1[P] } // { foo: string, bar: string }
type E2 = { foo: number; bar: number }
type T2 = { [P in keyof E1]: E2[P] } // { foo: number, bar: number }

type Point = { x: number; y: number }
type T3 = { [P in keyof Point]?: Point[P] } // { x?: number; y?: number }
// type Partial<T> = { [P in keyof T]?: T[P] }
type T4 = Partial<Point> // { x?: number; y?: number }

type PointPartial = { x?: number; y?: number }
type T5 = { [P in keyof PointPartial]-?: PointPartial[P] } // { x: number; y: number }
// type Required<T> = { [P in keyof T]-?: T[P] }
type T6 = Required<PointPartial> // { x: number; y: number }

type T7 = { readonly [P in keyof Point]: Point[P] } // { readonly x: number; readonly y: number }
// type Readonly<T> = { readonly [P in keyof T]: T[P] }
``````

### Key Remapping

``````type E1 = { foo: string; bar: string }
type T1 = { [P in keyof E1 as Uppercase<P>]: E1[P] } // { FOO: string; BAR: string }
type T2 = { [P in keyof E1 as Capitalize<P>]: E1[P] } // { Foo: string; Bar: string }
type T3 = { [P in keyof E1 as Exclude<P, 'foo'>]: E1[P] } // { bar: string }
type T4 = { [P in keyof E1 as Exclude<P, 'bar'>]: E1[P] } // { foo: string }
``````

Template Literal Types を使うと、remapping の意味がわかりやすい

``````type Point = { x: number; y: number }
type T5 = { [P in keyof Point as `ex_\${P}`]: Point[P] } // { ex_x: number; ex_y: number }
``````

## Conditional Types

``````// type NonNullable<T> = T extends null | undefined ? never : T
type T1 = NonNullable<string | number | undefined> // string | number
type T2 = NonNullable<string[] | null | undefined> // string[]

// type Extract<T, U> = T extends U ? T : never
type T3 = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
type T4 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'

// Exclude<T, U> = T extends U ? never : T
type T5 = Extract<'a' | 'b' | 'c', 'a'> // 'a'
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'b'> // 'a' | 'b'
``````

## `KeysMatching` を読み解く

``````type Example = { foo: number; bar?: string; baz?: number }

// -? で optional を無効に
type K1<T> = {
[K in keyof T]-?: T[K]
}
type T1 = K1<Example> // { foo: number; bar: string; baz: number }

// Conditional Types で
// V 型のプロパティの値をプロパティ名に、そうでないプロパティを never に
type K2<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}
type T2 = K2<Example, 'string'> // { foo: 'foo'; bar: never; baz: 'baz' }

// Indexed Access Types で取り出す
type KeysMatching<T, V> = {
[K in keyof T]-?: T[K] extends V ? K : never
}[keyof T]
type T3 = KeysMatching<Example, number> // 'foo' | 'baz'
``````

または

``````type Example = { foo: number; bar?: string; baz?: number }

// Key Remapping で T[P] が V 型であるものを取り出す
type K1<T, V> = {
[P in keyof T as T[P] extends V ? P : never]: any
}
type E1 = K1<Example, number> // {foo: any, baz?: any}

// keyof でプロパティ名を取り出す
// ( プロパティ名しか参照しておらず : any 部分は使っていないため、これは任意の型でいいことがわかる )
type K2<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: any
}
type E2 = K2<Example, number> // 'foo' | 'baz'
// 複雑な推論を伴う場合だと K が keyof T だと推論できないことがある
const M2 = <T, K extends K2<T, number>>(obj: T, key: K) => {
return obj[key] // TS2536: Type 'K' cannot be used to index type 'T'.
}

// & keyof T で K が keyof T であることを明示する
type KeysMatching<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: never
} &
keyof T
type E3 = K3<Example, number> // 'foo' | 'baz'
const M3 = <T, K extends K3<T, number>>(obj: T, key: K) => {
return obj[key] // OK
}
``````