: 타입에 삼항연산자(조건연산자) 적용
type IsString<T> = T extends string ? true : false
type A = isString<string> // true
type B = isString<number> // false
분배
string extends T ? A : B === string extends T ? A : B
(string | number) extends T ? A : B === (string extends T ? A : B)
| (number extends T ? A : B)
유니온 타입에 적용
// 조건부 타입x
type ToArray<T> = T[]
type A = ToArray<number> // number[]
type B = ToArray<number | string> // (number | string)[]
// 조건부 타입o
type ToArray2<T> = T extends unknown ? T[] : T[]
type A = ToArray2<number> // number[]
type B = ToArray2<number | string> // number[] | string[]
다양한 공통 연산을 안전하게 표현 가능
type Without<T, U> = T extends U ? never : T
type A = Without<boolean | number | string, boolean> // number | string
//---과정 ---//
type A = Without<boolean, boolean>
| Without<number, boolean>
| Without<string, boolean>
= (boolean extends boolean ? never : boolean)
| (number extends boolean ? never : boolean)
| (string extends boolean ? never : boolean)
= never
| number
| string
= number | string
infer 키워드
: 조건부 타입에서 제네릭 타입을 인라인으로 선언하는 전용 문법
// inter 적용x
type ElementType<T> = T extends unknown[] ? T[number] : T
type A = ElementType<number[]> // number
// infer 적용o
type ElementType2<T> = T extends (infer U)[] ? U : T
type B = ELementType2<number[]> // number
// 인라인으로 선언x 경우
type ElementUgle<T, U> = T extends U[] ? U : T
type C = ElementUgly<number[]> // Error: 제네릭 타입 'ElementUgly'는
// 두 개의 타입 인수를 필요로 함
복잡한 예제
// 두번째 인수의 타입 얻기
type SecondArg<F> = F extends (a: any, b: infer B) => any ? B : never
// Array.slice의 타입 얻기
type F = typeof Array['prototype']['slice']
type A = SecondArg<F> // number | undefined
내장 조건부 타입들
Exclude<T, U> // 앞서 작성한 Without 타입처럼 T에 속하지만 U에는 없는 타입을 구함
type A = number | string
type B = string
type C = Exclude<A, B> // number
Extract<T, U> // T의 타입 중 U에 할당할 수 있는 타입을 구함
type A = number | string
type B = string
type C = Extract<A, B> // string
NonNullable<T> // T에서 null과 undefined를 제외한 버전을 구함
type A = {a?: number : null}
type B = NonNullable<A['a']> // number
ReturnType<F> // 함수의 반환 타입을 구함(제네릭과 오버로드된 함수에서는 동작x)
type F = (a: number) => string
type R = ReturnType<F> // string
InstanceType<C> // 클래스 생성자의 인스턴스 타입을 구함
type A = {new(): B}
type B = {b: number}
type I = InstanceType<A> // {b: number}
function formatInput(input: string) { ... }
function getUserInput(): string | number { ... }
let input = getUserInput()
formatInput(input as string)
formatInput(<string>input)
Nonnull 어서션
type Dialog = { id?: string }
function closeDialog(dialog: Dialog) {
if(!dialog.id) { return }
// === 여기서 어떤 코드를 통해 dialog가 변경될 수 있음 === //
// 화살표 함수 내부이므로 유효범위가 바뀌면서 if문을 통한 정제가 무효화됨
setTimeout(() =>
removeFromDom(dialog, document.getElementById(dialog.id)) // Error: 'string | undefined' 타입의 인수는
// 'string' 타입의 매개변수에 할당할 수 없음
)
}
function removeFromDOM(dialog: Dialog. element: Element) {
element.parentNode.removeChild(element) // Error: 객체가 'null'일 수 있음
delete dialog.id
}
// Nonnull 어서션 연산자(!)를 사용하여 해결
function closeDialog(dialog: Dialog) {
if(!dialog.id) { return }
setTimeout(() =>
removeFromDom(dialog, document.getElementById(dialog.id!)!)
)
}
어서션을 사용하지 않고 정제
type VisibleDialog = {id: string}
type DestoryedDialog = {}
type Dialog = VisibleDialog | DestoryedDialog
function closeDialog(dialog: Dialog) {
// dialog는 VisibleDialog 타입이 됨
if(!('id' in dialog) { return }
// 화살표 내부의 dialog는 외부의 dialog와 같은 값 => 정제 이어짐
setTimeout(() =>
removeFromDOM(dialog, document.getElementById(diablog.id)!)
)
}
function removeFromDOM(dialog: VisibleDialog, element: Element) {
element.parentNode!.removeChild(element)
delete dialog.id
}
확실한 할당 어서션
let userId: string
userId.toUpperCase() // Error: 할당하지 않고 'userId' 변수를 사용함
// 어서션을 사용하여 타입스크립트에 변수에 값이 반드시 있다는 것을 알려줌
let userId!: string
fetchUser()
userId.toUpperCase()
function fetchUser() { // 반드시 userId가 정의됨을 보장한다고 가정
userId = globalCache.get('userId')
}
: 타입스크립트는 구조 기반 타입
=> 타입의 구조만 같다면 에러 발생x
type CompanyId = string
type OrderId = string
type UserID = string
type ID = CompanyID | OrderId | UserId
function queryForUser(id: UserId) {
...
}
let id: Company = 'b12345678'
queryForUser(id) // Error 발생 안함
type CompanyId = string & { readonly brand: unique symbol }
type OrderId = string & { readonly brand: unique symbol }
type UserId = string & { readonly brand: unique symbol }
type ID = CompanyId | OrderId | UserId
// 타입 어셔션(as)를 사용하여 브랜디드 타입의 생성자 생성
function CompanyID(id: string) {
return id as CompanyID
}
function OrderID(id: string) {
return id as OrderId
}
function UserID(id: string) {
return id as UserID
}
function queryForUser(id: UserID) {
...
}
let companyID = CompanyID('a1234567')
let orderId = OrderID('qwer123')
let userId = UserID('zcxv45678')
queryForUser(userId)
queryForUser(companyId) // Error: 'Compnay ID' 타입의 인수를 'UserID' 타입의 매개변수에 할당할 수 없음
.ts 파일에서 프로토타입 확장
// 전역 범위로 정의된 Array<T> 인터페이스에 zip 메서드 추가(인터페이스 합치기 기능 이용)
interface Array<T> {
zip(U)(list: U[]): [T, U][]
}
// 만약 zip을 구현하는데 뭔가를 import 해야하는 상황이라면
// 전역 확장을 declare global이라는 타입 선언으로 감싸야 함
declare global {
interface Array<T> {
zip(U)(list: U[]): [T, U][]
}
}
새로운 메서드를 프로토타입에 추가
function tuple<T extends unknown[]<(...ts: T):T {
return ts
}
// this 타입을 사용하여 타입스크립트가 .zip이 호출되는 대상 배열에서
// T 타입을 올바로 추론할 수 있도록 함
Array.prototype.zip = function <T, U>(this: T[], list: U[]): [T, U][] {
return this.map((v, k) => tuple(v, list[k]))
}
Array.prototype에 기능을 추가하려면 zip을 사용하는 모든 파일이 zip.ts를 먼저 로드해야함
=> 프로젝트에서 zip.ts를 명시적으로 제외하도록 tsconfig.json 수정
=> 이 기능을 사용하는 쪽에서 명시적으로 import 해야함
{
* exclude *: [
"./zip.ts"
]
}
다음과 같이 사용
import './zip'
[1, 2, 3].map(n => n * 2) // number[]
.zip(['a', 'b', 'c']) // [number, string][]