LoginSignup
5
3

More than 1 year has passed since last update.

【TypeScript】typeof, keyof, in の挙動に関する備忘録

Last updated at Posted at 2021-12-14

前提

以下のような定義があるとする。

type User = {
  name: string
  age: number
}

const user: User = {
  name: 'example',
  age: 20
}

typeof

型コンテキストでの使用

変数の型を取得することができる。

type User2 = typeof user // User2 は User型となる

console.log(typeof user)                   // object
console.log(typeof user.name)              // string
console.log(typeof user === 'object')      // true
console.log(typeof user.name === 'string') // true

keyof

オブジェクトのプロパティ名を抽出し、文字列リテラルのユニオン型(String Literal Union) を取得することができる。

let keys: keyof User
keys = 'age'
keys = 'name'
keys = 'XXX' // 型 '"XXX"' を型 'keyof User' に割り当てることはできません。

// 例) GraphQL スキーマから TypeScript の型を生成するビルドツールを使っている場合
type APIResponse = {
  user: {
    id: string
    friendList: {
      count: number
      friends: {
        firstName: string
        lastName: string
      }[]
    }
  }
}

type ResponseKeys = keyof APIResponse    // 'user'
type UserKey = keyof APIResponse['user'] // 'id' | 'friendList'

ルックアップ型 × keyof

型安全なゲッター関数を実装することができる。

function get<
  O extends object,
  K extends keyof O
>(
  o: O,
  k: K
): O[K] {
  return o[k]
}

const res: APIResponse = {
  user: {
    id: 'sample',
    friendList: {
      count: 1,
      friends: [
        {
          firstName: '',
          lastName: ''
        }
      ]
    }
  }
}

const user = get(res, 'user')
console.log(user) // { id: 'sample', friendList: { count: 1, friends: [ [Object] ] } }

ネストの深い階層を取得するために、複数の引数を取れるようにしてみる。

type Get = {
  <
    O extends object,
    K1 extends keyof O
  >(o: O, k1: K1): O[K1]
  <
    O extends object,
    K1 extends keyof O,
    K2 extends keyof O[K1]
  >(o: O, k1: K1, k2: K2): O[K1][K2]
  <
    O extends object,
    K1 extends keyof O,
    K2 extends keyof O[K1],
    K3 extends keyof O[K1][K2]
  >(o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3]
}

const get: Get = (object: any, ...keys: string[]) => {
  let result = object
  keys.forEach((k) => {
    result = result[k]
  })
  return result
}

const abc = get(res, 'user', 'friendList', 'count')
console.log(abc) // 1

in

オブジェクトのプロパティ存在有無を判定する

console.log('name' in user); // true
console.log('xxx' in user); // false

for で全プロパティを処理する

for (const key in user) {
  console.log(key) // name\n age
}

Mapped types

type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'

const nextDay: { [K in Day]: Day } = {
  Mon: 'Tue',
  Thu: 'Wed',
  Wed: 'Thu',
  Tue: 'Fri',
  Fri: 'Sat',
  Sat: 'Sun',
  Sun: 'Mon'
}

keyof × in

mapped type は TypeScript 特有の言語機能で、それらは、リテラル型と同様に JavaScript を静的に型付けする試みにとって有用なユーティリティ機能である。これはとても強力である。なぜならオブジェクトのキーと値に型を与えるだけでなくルックアップ型とそれらを組み合わせると、どの値の型がどのキーの名前に対応するかを制約できるからである。

type Account = {
  id: number
  isEmployee: boolean
  notes: string[]
}

// 全てのフィールドを省略可能にする
type OptionalAccount = {
  [K in keyof Account]?: Account[K]
}

// 全てのフォールドをnull可能にする
type NullableAccount = {
  [K in keyof Account]: Account[K] | null
}
5
3
0

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
5
3