타입 넓히기
let a = 'x' // string
let b = 3 // number
var c = true // boolean
const d = {x: 3} // {x: number}
enum E {X, Y, Z}
let e = E.X // E
const a = 'x' // 'x'
let b = a // string
let a = null // any
a = 3 // any
a = b // any
타입을 넓히지 않기
값을 바꿀 수 없는 변수 사용
const a = 'x' // 'x'
const b = 3 // 3
const c = true // true
enum E {X, Y, Z}
const e = E.X // E.X
명시적으로 타입 어노테이션 추가
const a: 'x' = 'x' // 'x'
let b = a // 'x'
unll, undefined로 초기화된 변수가 선언 범위를 벗어난 경우
function x() {
let a = null // any
a = 3 // any
a = b // any
return a
}
x() // string
const 타입 사용: 타입 넓히기 중지 & 멤버들이 자동으로 readonly가 됨
let a = {x: 3} as const 4 // {readonly x: 3}
let b = [1, {x: 2}] as const // readonly [1, {readonly x: 2}]
: 타입을 정제할 때 typeof, instanceof, in 등의 타입 질의뿐만 아니라 if, ?, ||, switch 같은 제어 흐름 문장을 고려함
type Unit = 'cm' | 'px' | '%'
let units = Unit[] = ['cm', 'px', '%']
function parseUnit(value: string): Unit | null {
for (let i = 0; i < units.length; i++) {
if (value.endsWith(units[i])) {
return units[i]
}
}
return null
}
type Width = {
unit: Unit,
value: number
}
function parseWidth(width: number | string | null | undefined): Width | null {
if (width == null) {
return null
}
if (typeof width == 'number') {
return { unit: 'px', value: width }
}
let unit = parseUnit(width)
if (unit) {
return { unit, value: parseFloat(width) }
}
return null
}
type UserTextEvent = { value: string, target: HTMLInputElement }
type UserMouseEvnet = { value: [number, number], target: HTMLElement }
type UserEvent = UserTextEvent | UserMouseEvent
function handle(event: UserEvent) {
if (typeof event.value === 'string') {
event.value // string
event.target // HTMLInputElement | HTMLElement
return
}
event.value // [number, number]
event.target // HTMLInputElement | HTMLElement
}
// 문제: event.target이 제대로 정제되지 않음
리터럴 타입을 이용해 유니온 타입이 만들어낼 수 있는 각각의 경우를 태그하는 방식으로 문제 해결
// TextEvent라는 리터럴 타입 생성
type UserTextEvent = { type: 'TextEvent', value: string, target: HTMLInputElement }
// MouseEvent라는 리터럴 타입 생성
type UserMouseEvnet = { type: 'MouseEvent', value: [number, number], target: HTMLElement }
type UserEvent = UserTextEvent | UserMouseEvent
function handle(event: UserEvent) {
if (event.type === 'TextEvent') {
event.value // string
event.target // HTMLInputElement
return
}
event.value // [number, number]
event.target // HTMLElement
}
// 문제: event.target이 제대로 정제되지 않음
키인 연산자
type APIresponse = {
user: {
userId: string
friendList: {
count: number
friends: {
firstName: string
lastname: string
}[]
}
}
}
type FriendList = APIResponse['user']['friendList']
function renderFriendList(friendList: FriendList) {
...
}
// 친구 한 명의 타입
type Friend = FriendList['friends'][number]
keyof 연산자
객체의 키를 문자열 리터럴 타입 유니온으로 얻는 방법
type ResponseKeys = keyof APIResponse // 'user'
type UserKeys = keyof APIResponse['user'] // 'userId' | 'friendList'
type FriendListKeys = keyof APIResponse['user']['friendList'] // 'count' | 'friends'
객체에서 주어진 키에 해당하는 값 반환
function get<O extends object, K extends keyof O>(o: O, k: K): O[K] {
return o[k]
}
type ActivityLog = {
lastEvent: Date
events: {
id: string
timestamp: Date
type: 'Read' | 'Write'
}[]
}
let activityLog: ActivityLog = ...
let lastEvent: get(activityLog, 'lastEvent') // Date 타입 반환
// 키를 세 개까지 받을 수 있도록 get 오버로드
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]
}
let get: Get = (object: any, ...keys: string[]) => {
let result = object
keys.forEach(k => result = result[k])
return result
}
get(activityLog 'events', 0, 'type') // 'Read' | 'Write'
get(activityLog, 'bad') // Error: 인수 'bad' 타입은 매개변수 'LastEvent' | 'events' 타입에 할당할 수 없음
매핑된 타입
type Weekday = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'
let nextDay: { [K in Weekday]: Day } = { // Error: '{Mon: "Tue"}' 타입은
Mon: 'Tue' // '{Mon: Weekday; Tue: Weekday; Wed: Weekday; Thu: Weekday; Fri: Weekday}' 타입이 정의한 프로퍼티 중
} // Tue, Wed, Thu, Fri를 포함하지 않음
활용 예제
type Account = {
id: number
isEmployee: boolean
notes: string[]
}
// 모든 필드를 선택형으로 만듦
type OptionalAccount = {
[K in keyof Account]?: Account[k]
}
// 모든 필드를 nullable로 만듦
type NullableAccount = {
[K in keyof Account]: Account[k] | null
}
// 모든 필드를 읽기 전용으로 만듦
type ReadonlyAccount = {
readonly [K in keyof Account]: Account[k]
}
// 모든 필드를 다시 쓸 수 있도록 만듦(= Account)
type Account2 = {
- readonly [K in keyof ReadonlyAccount]: Account[k]
}
// 모든 필드를 다시 필수형으로 만듦(= Account)
type Account3 = {
[K in keyof OptionalAccount]-?: Account[k]
}
내장 매핑된 타입
Record<Keys, Values> // Keys 타입의 키와 values 타입의 값을 갖는 객체
Partial<Object> // Object의 모든 필드를 선택형으로 표시
Required<Object> // Object의 모든 필드를 필수형으로 표시
Readonly<Object> // Object의 모든 필드를 읽기 전용으로 표시
Pick<Object, Keys> // 주어진 Keys에 대응하는 Object의 서브타입을 반환
튜플의 타입 추론 개선
: 타입스크립트는 튜플의 길이, 타입 위치를 무시하고 제공할 수 있는 가장 일반적인 타입으로 튜플의 타입 추론
let a = [1, true] // (number | boolean)[]
// 타입 어서션, as const를 사용하지 않는다면
// 나머지 매개변수의 타입을 추론하는 기법 이용
function tuple<T extends unknown[]>(...ts: T):T {
return ts
}
let b = tuple(1, true) // [number, boolean]
사용자 정의 타입 안전 장치
: 타입 정제는 유효범위에 속한 변수만을 처리 가능
: 한 영역에서 다른 영역으로 이동하면 기존의 정제 결과물은 사라짐
function isString(a: unknown): boolean {
return typeof a === 'string'
}
isString('a') // true
isString([7]) // false
function parseInput(input: string | number) }
let formattedInput: string
// 타입스크립트는 isString이 boolean을 반환한다는 사실만 알 수 있음
if (isString(input)) {
formattedInput = input.toUpperCase() // Error: 'number' 타입에 'toUpperCase' 프로퍼티가 존재하지 않음
}
}
// 사용자 안전 장치 기법으로 해결 가능
function isString(a: unknown): a is string {
return typeof a === 'string'
}
type LegacyDialog = ...
type Dialog = ...
function isLegacyDialog(dialog: LegacyDialog | Dialog): dialog is LegacyDialog {
...
}