Programming TypeScript ch6 (2/3) Advanced Function Types
Totality
- switch-caseの漏れとかを教えてくれるやつ
type Weekday = 'Mon' | 'Tue' | 'Wed'|'Thu'|'Fri' type Day = Weekday | 'Sat' | 'Sun' // Error: Function lacks ending return statement and return type does not include 'undefined'. function getNextDay(w: Weekday): Day { switch(w) { case 'Mon': return 'Tue' } }
TSC Flag: noImplicitReturns
- tsconfigで
"noImplicitReturns": true
しておくと、下記のような潜在的な不具合を検出できる
// Error: Not all code paths return a value. function isBig(n: number) { if (n >= 100) { return true } }
Advanced Object Types
Type Operators for Object Types
type APIResponse = { user: { userId: string, friendList: { count: number, friends: { firstName: string, lastName: string }[] } } } function getAPIResponse(): Promise<APIResponse> { // ... } function renderFriendList(friendList: any) { // どうする? // ... } const response = await getAPIResponse() renderFriendList(response.user.friendList)
friendList
のtypehintをどうする?- とりあえず
any
でスタブにしている
- とりあえず
FriendList
を定義する?
type FriendList = { count: number, friends: { firstName: string, lastName: string }[] } type APIResponse = { user: { userId: string, friendList: FriendList } } function getAPIResponse(): Promise<APIResponse> { // ... } function renderFriendList(friendList: FriendList) { // ... } const response = await getAPIResponse() renderFriendList(response.user.friendList)
- いちいち名前をつけたくないこともある
type APIResponse = { user: { userId: string, friendList: { count: number, friends: { firstName: string, lastName: string }[] } } } function getAPIResponse(): Promise<APIResponse> { // ... } function renderFriendList(friendList: APIResponse['user']['friendList']) { // ... } const response = await getAPIResponse() renderFriendList(response.user.friendList)
The keyof operator
type APIResponse = { user: { userId: string, friendList: { count: number, friends: { firstName: string, lastName: string }[] } } } type ResponseKeys = keyof APIResponse // 'user' type UserKeys = keyof APIResponse['user'] // 'userId'|'friendList' type FriendListKeys = keyof APIResponse['user']['friendList'] // 'count'|'friends'
- 用法
type Vector3D<T = number> = { x: T, y: T, z: T } function get<T>(vector3d: Vector3D<T>, key: keyof Vector3D<T>): T { return vector3d[key] } const v: Vector3D = { x: 1, y: 2, z: 3, } const x = get(v, 'x') const y = get(v, 'y') const z = get(v, 'z') const w = get(v, 'w') // Error: Argument of type '"w"' is not assignable to parameter of type '"x" | "y" | "z"'.
TSC Flag: keyofStringsOnly
keyof
はデフォルトでnumber|string|symbol
を返す- 配列の例
type KeysOfArray<T> = keyof T[] // number|'length'|'toString'|... type NumericKeysOfArray<T> = number & keyof T[] // number
- stringだけにしたい場合、tsconfigで
"keyofStringsOnly": true
する
type KeysOfArray<T> = keyof T[] // 'length'|'toString'|... type NumericKeysOfArray<T> = number & keyof T[] // never
The Record Type
- nextDayをswitch-caseじゃなくてルックアップテーブルで書き換えたいぞ、になったとき
- totalityの担保のために使える組み込みの型
type Weekday = 'Mon' | 'Tue' | 'Wed'|'Thu'|'Fri' type Day = Weekday | 'Sat' | 'Sun' // Error: Type '{ Mon: "Tue"; }' is missing the following properties from type 'Record<Weekday, Day>': Tue, Wed, Thu, Fri let nextDay: Record<Weekday, Day> = { Mon: 'Tue' }
- 網羅するとコンパイルエラーが消える
let nextDay: Record<Weekday, Day> = { Mon: 'Tue', Tue: 'Wed', Wed: 'Thu', Thu: 'Fri', Fri: 'Sat' }
Mapped Types
- Record Typesよりももっと強力なやつ
type Weekday = 'Mon' | 'Tue' | 'Wed'|'Thu'|'Fri' type Day = Weekday | 'Sat' | 'Sun' // 3: typescript: Property 'Fri' is missing in type '{ Mon: "Tue"; Tue: "Wed"; Wed: "Thu"; Thu: "Fri"; }' but required in type '{ Mon: Day; Tue: Day; Wed: Day; Thu: Day; Fri: Day; }'. let nextDay: {[Key in Weekday]:Day} = { Mon: 'Tue', Tue: 'Wed', Wed: 'Thu', Thu: 'Fri', }
- 既存の型のoptionalやreadonlyを付け外ししたりできる
type Account = { id: number, isEmployee: boolean, notes: string[] } type OptionalAccount = { [K in keyof Account]?: Account[K] } type NullableAccount = { [K in keyof Account]: Account[K] | null } type ReadonlyAccount = { readonly [K in keyof Account]: Account[K] } // equivalent to Account type Account2 = { [K in keyof OptionalAccount]-?: OptionalAccount[K] } // equivalent to Account type Account3 = { -readonly [K in keyof ReadonlyAccount]: ReadonlyAccount[K] }
Built-in mapped types
type Account = { id: number, isEmployee: boolean, notes: string[] } type OptionalAccount = Partial<Account> type ReadonlyAccount = Readonly<Account> // equivalent to Account type Account2 = Required<OptionalAccount> type AccountId = Pick<Account, 'id'> // { id: number; }
Companion Object Pattern
type Currency = { unit: 'EUR' | 'GBP' | 'JPY' | 'USD' value: number } const Currency = { from(value: number, unit: Currency['unit'] = 'USD'): Currency { return { unit, value } } }
- 型と値の名前空間は異なるので、同名にできる
- まとめてimportできるのが嬉しい
Currency.ts
export type Currency = { unit: 'EUR' | 'GBP' | 'JPY' | 'USD' value: number } export const Currency = { from(value: number, unit: Currency['unit'] = 'USD'): Currency { return { unit, value } } }
index.ts
import { Currency } from './Currency' const amountDue: Currency = { unit: 'JPY', value: 83733.10 } const otherAmouhtDur = Currency.from(330, 'EUR')