身内の勉強会用の資料です。
PlayGroundでコードを動かすことで、なんとなく何が起こるかを知るための資料です。
まともな説明はしないので、ちゃんと知りたい人は公式サイトとか読みましょう。(今回紹介したコードもだいたい公式サイトに載ってます)
本文の一番下に動作確認用のコピペコードがあるので、PlayGroundに貼り付けて動作を確認してみましょう。
また、ここで紹介する以外にもTypeScriptの組み込み関数には以下のようなものがあるので興味があれば見てみてください
型のおさらい
以下のようなUserオブジェクトを使って例示する
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']
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}
オブジェクトのキーを推論する
上で紹介した 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関数をもつDocumentWrapper
classを作っておく
// 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という定数は存在しないのでエラー