LoginSignup
3
3

More than 3 years have passed since last update.

PlayGroundで覚えるTypeScriptの高度な型を使った便利な記法

Last updated at Posted at 2019-10-05

身内の勉強会用の資料です。

PlayGroundでコードを動かすことで、なんとなく何が起こるかを知るための資料です。
まともな説明はしないので、ちゃんと知りたい人は公式サイトとか読みましょう。(今回紹介したコードもだいたい公式サイトに載ってます)

本文の一番下に動作確認用のコピペコードがあるので、PlayGroundに貼り付けて動作を確認してみましょう。

また、ここで紹介する以外にもTypeScriptの組み込み関数には以下のようなものがあるので興味があれば見てみてください

型のおさらい

以下のようなUserオブジェクトを使って例示する


const user = {
  name: 'testuser',
  id: 1,
  obj: {
    obj2: {
      aaa: 'aaaa'
    },
    obj3:{
      ccc: 'cccc'
    },
    bbb: 'bbbb',
  }
}

typeof

オブジェクトの型を返す。以下のような感じ

type User = typeof user

image.png

keyof

オブジェクト型のキーをUnion Typeに変換する。以下のような感じ

type A = keyof User

image.png

T[K]

オブジェクト型のプロパティの型を返す。以下のような感じ

type B = User['name']

image.png

in

{[K in (Union Type)]: P} はUnion TypeをMapに展開する。以下のような感じ

type C = {[K in keyof User]: string}
type D = {[K in keyof User]: K}
type E = {[K in keyof User]: User[K]}   // = User型と同じ
type F = {[K in keyof User]: User}

image.png

image.png

image.png

image.png

オブジェクトのキーを推論する

上で紹介した keyof などは以下のような時に使える

/**
 * 引数に指定したオブジェクトのキーの値を所得する
 */
function propGet<T, K extends keyof T>(obj: T, key: K): T[K]{
    return obj[key]
}

このような関数の型を上のようにつけておくと、以下のように、第一引数の型を使って賢く推論してくれる

propGet(user,'name') // => 返り値の型がstringになる
propGet(user,'id')   // => 返り値の型がnumberになる
propGet(user,'nana') // => nanaというプロパティは存在しないのでコンパイルエラー

classの中で使う

以下のようなgetProp関数をもつDocumentWrapperclassを作っておく

// getPropを定義したDocumentWrapper
abstract class DocumentWrapper {
  /**
   * 引数に指定したキーの値を所得する
   */
  getProp<T extends keyof this>(key: T): this[T]{
    return this[key]
  }
}

ちなみにここではthis型は自分自身の型を返してくれる

このDocumentWrapperを継承したclassを作る

class Person extends DocumentWrapper {
  id: number | null = null
  name = ''
  obj = {
    aaa: 'aaa',
    bbb: 123
  }
}

以下のように使える

const person = new Person()

person.getProp('id') // => 返り値はnumber | null型
person.getProp('name') // => 返り値はstring型
person.getProp('nana') // => nanaというプロパティはPersonに存在しないのでコンパイルエラー

定数の推論を行う

keyとvalueが同じ値をもつ連想配列を生成する関数を以下のように作り、定数として利用できるようにする

/**
 * 引数にいれた文字列の配列をkeyとvalueにもつオブジェクトを返す
 */
function generateEnum<T extends string>(strArr :T[]): {[K in T]: K } {
    return strArr.reduce((acc, cur) => {
    acc[cur] = cur;
    return acc;
  }, Object.create(null));
}

以下のように使うことができる

const POSITION = generateEnum(['top','center','bottom'])

これは以下と同じ、

const POSITION = {
    top: 'top',
    center: 'center',
    bottom: 'bottom'
}

この定数のみをUnion Typeとして引数や返り値に指定したい場合は、 keyof typeof を使うことができる。
以下のような感じ

function reversePosition(position: keyof typeof POSITION): keyof typeof POSITION{
  switch(position){
    case 'top': {
      return 'bottom'
    }
    case POSITION.center: {
      return 'center'
    }
    case 'bottom': {
      return 'top'
    }
  }
}
reversePosition('top') // => topという文字列は定数の中に存在するのでok
reversePosition(POSITION.top) // => 当然POSITION.topを指定してもok
reversePosition('button') // => buttonという定数は存在しないのでコンパイルエラー

コピペ用

動作確認用のコピペコード


const user = {
  name: 'testuser',
  id: 1,
  obj: {
    obj2: {
      aaa: 'aaaa'
    },
    obj3:{
      ccc: 'cccc'
    },
    bbb: 'bbbb',
  }
}

// typeof はオブジェクトの型を返す
type User = typeof user

// keyof は型のメンバをUnion Typeで返す
type A = keyof User

// T[K]はプロパティアクセス型になる
type B = User['name']

// {[K in (Union Type)]: P} はUnion TypeをMapに展開する
type C = {[K in keyof User]: string}
type D = {[K in keyof User]: K}
type E = {[K in keyof User]: User[K]}   // = User型と同じ
type F = {[K in keyof User]: User}




// Prop.get()みたいなやつを扱う

/**
 * 引数に指定したオブジェクトのキーの値を所得する
 */
function propGet<T, K extends keyof T>(obj: T, key: K): T[K]{
    return obj[key]
}

// 第2引数を 'name' | 'id' | 'obj' のようにきちんと推論してくれる
// 返り値のidのほうも number型としてきちんと推論してくれる
const id = propGet(user,'id')

const age = propGet(user,'age') // 'age' は存在しないのでエラー

if (propGet(user,'name') === 9){} // nameプロパティの型はstringなので、この条件式は常にfalseになるため、エラーになる


// classの中でProp.getを扱う

// getPropを定義したDocumentWrapper
abstract class DocumentWrapper {
  /**
   * 引数に指定したキーの値を所得する
   */
  getProp<T extends keyof this>(key: T): this[T]{
    return this[key]
  }
}

class Person extends DocumentWrapper {
  id: number | null = null
  name = ''
  obj = {
    aaa: 'aaa',
    bbb: 123
  }
}

const person = new Person()

// 第引数を 'name' | 'id' | 'obj' | getProp のようにきちんと推論してくれる
// 返り値のidのほうも number | null型としてきちんと推論してくれる
const personId = person.getProp('id')



// 定数を扱う

/**
 * 引数にいれた文字列の配列をkeyとvalueにもつオブジェクトを返す
 */
function generateEnum<T extends string>(strArr :T[]): {[K in T]: K } {
    return strArr.reduce((acc, cur) => {
    acc[cur] = cur;
    return acc;
  }, Object.create(null));
}


const POSITION = generateEnum(['top','center','bottom'])

function reversePosition(position: keyof typeof POSITION): keyof typeof POSITION{
  switch(position){
    case 'top': {
      return 'bottom'
    }
    case POSITION.center: {
      return 'center'
    }
    case 'bottom': {
      return 'top'
    }
  }
}

reversePosition('top') // => topという文字列は定数の中に存在するのでok
reversePosition(POSITION.top) // => 当然POSITION.topを指定してもok
reversePosition('button') // => buttonという定数は存在しないのでエラー

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