타입스크립트는 세계 최고 수준의 타입 시스템을 자랑한다
두 개의 타입 A, B 가 있고 B가 A의 서브타입이면 A 가 필요한 곳에는 어디든
B 를 안전하게 사용할 수 있다
두 개의 타입 A, B 가 있고 B가 A 의 슈퍼타입이면 B 가 필요한 곳에는 어디든
A를 안전하게 사용할 수 있다
B: 슈퍼타입
A: 서브타입
보통 A 라는 타입이 B 라는 다른 타입의 서브타입인지 아닌지 쉽게 판단할 수 있다
단순 타입은 흐름도로 확인하거나 자체적으로 쉽게 추론 가능하다
number 는 number | string 의 서브타입이다
매개변수화된 (제네릭) 타입 등 복합 타입에서는 조금 복잡해진다
다른 타입을 포함하는 Array<A> 같은 타입의 서브타입 규칙을 추론하기는 어렵고
프로그래밍 언어마다 조금씩 차이가 있다
타입스크립트의 타입 규칙은 안전성을 추구하도록 설계되었다기보다
실수를 캐치하는 것과 쉬운 사용 두 가지를 목표로 한 것
안전성이 보장되지 않는 특정한 상황은 실용적인 면에서 타당성이 있다
프로퍼티 삭제 등의 파괴적 갱신은 실무에서 비교적 드물게 일어나므로
타입스크립트는 이를 적극 제지하지 않고
슈퍼타입이 필요한 곳에 객체를 할당할 수 있도록 허용한다
type ExistingUser = {
id: number
name: string
}
type NewUser = {
name: string
}
위와 같은 유저를 삭제한다고 할 때
function deleteUser(user: {id?: number, name: string}) {
delete user.id
}
let existingUser: ExistingUser = {
id: 123,
name: 'string'
}
deleteUser(existingUSer)
deleteUser 의 user 매개변수 id 프로퍼티 타입이 (number | undefined) 의
서브타입이 되므로 전체 객체 existingUser 도 에러를 발생시키지 않는다
ExistringUser 를 deleteUser 로 전달, 삭제 후에도
타입스크립트는 user 의 id 가 삭제된 사실을 모른다
따라서 id 삭제 후에도 existingUser 의 id 를 타입스크립트는 number 타입으로
생각한다
반대 방향 ( 서브타입이 필요한 곳에 할당 ) 하는 경우는 다음과 같다
type LegacyUser = {
id?: number | string
name: string
}
let legacyUser: LegacyUser = {
id: '123',
name: 'name'
}
deleteUser(lagacyUser) // 에러 TS2346
기대하는 타입의 슈퍼타입의 프로퍼티를 포함하는 형태를 전달하면
타입스크립트는 에러를 발생시킨다
슈퍼타입의 id 는 number | string | undefined 인 반면
deleteUser 는 id 가 number | undefined 인 경우에만 처리할 수 있다
요구되는 프로퍼티 타입 각각에 대해 기대되는 타입이 요구되는 타입과 같거나 포함되어야 한다
즉 정의된 객체의 프로퍼티 타입이거나 해당 타입의 서브타입만 사용할 수 있다
슈퍼타입은 사용할 수 없다
타입과 관련해 타입스크립트 형태 (객체와 클래스) 는 그들의 프로퍼티 타입에 공변한다고 말한다
(공변성)
타입스크립트에서 모든 복합 타입의 멤버 (객체, 클래스, 배열, 함수, 반환 타입) 는
공변이며 함수 매개변수 타입만 예외적으로 반변이다
모든 언어가 타입스크립트 처럼 동작하지는 않는다
함수 A 가 함수 B 와 같거나적은 수의 매개변수를 가지며 다음을 만족하면
A 는 B 의 서브타입이다
반환타입만 조건이 반대다
객체, 배열, 유니온 등과 달리 함수에서는 반환 타입을 포함한 모든 컴포넌트가
A <: B (A 는 B와 같거나 B의 서브타입) 를 만족하지 않는다
객체의 경우 A <: B 관계가 성립된다
슈퍼타입의 정보는 서브타입이 갖고있지만
서브타입의 정보는 슈퍼타입이 갖고있지 않다
함수의 경우
class Animal {}
class Bird extends Animal {
chirp() {}
}
class Crow extends Bird {}
function chirp(bird: Bird): Bird {
bird.chirp()
return bird
}
chirp(new Animal) // error (슈퍼타입은 사용할 수 없다)
function clone(f: (b: Bird) => Bird): void {}
function birdToBird(b: Bird): Bird {}
clone(birdToBird) // ok
function birdToCrow(b: Bird): Crow {}
clone(birdToCrow) // ok
function birdToAnimal(b: Bird): Animal {}
clone(birdToAnimal) // 에러 TS2345
birdToAnimal 은 에러간 난다
클론 함수가 다음과 같을 떄
적어도 babyBird 는 chirp() 를 사용할 수 있어야 한다
그러려면 슈퍼타입인 Animal 타입은 사용할 수 없다
function clone(f: (b: Bird) => Bird): void {
let parent = new Bird
let babyBird = f(parent)
babyBird.chirp()
}
함수의 반환 타입은 공변을 만족해야 한다