목차
반응형
1. 타입 간의 관계
1. 1 서브 타입과 슈퍼 타입
- A가 필요한 곳에 B를 사용할 수 있다면 A는 슈퍼 타입, B는 서브 타입이다.
- 예를 들어, 객체를 사용해야 하는 곳에 배열도 사용할 수 있다. 따라서 이 경우 객체는 슈퍼 타입, 배열은 서브 타입이다.
- 모든 것은 any의 서브타입이고, never는 모든 것의 서브타입이다. 즉, 모든 타입을 any에 할당할 수 있고, never는 모든 타입에 할당할 수 있다.
1.2 가변성
- 단순 타입은 이 관계를 파악하기 쉽다. 예) number 는 number | string 유니온의 서브 타입이다.
- 하지만 객체나 함수와 같은 복합 타입의 경우 이 관계를 파악하기가 복잡할 수 있다. 복합 타입의 서브타입 규칙은 언어마다 다르다.
type ExistingUsesr = {
id: number
name: string
}
type NewUser = {
name: string
}
function deleteUser(user: {id?:number, name: string}) {
delete user.id
}
let existingUser:ExistingUsesr = {
id: 12345,
name: 'Ima User'
}
/**
* {id: number | undefined, name: string }은 슈퍼타입
* {id: number,name: string }은 서브타입. (각각의 프로퍼티를 비교)
*
* 서브타입 -> 슈퍼타입에 할당 가능 (공변성)
*/
deleteUser(existingUser)
const id = existingUser.id // 삭제되었지만 number 타입으로 추론하는 문제
형태와 배열 가변성
- 가변성 : 타입과 서브타입의 관계 파악하기
- 불변
- 정확히 T만 할당 가능
- 공변(대부분의 경우)
- A가 B의 서브타입이면, T<A>는 T<B>의 서브타입이다.
- 예를 들어, string은 string | number의 서브타입인 경우를 보자. Array<string>은 Array<string | number>의 서브타입이고, { a: string, b: number }도 { a: string | number, b: number }의 서브타입이 된다.
- 반변
- A가 B의 서브타입이면, T<B>는 T<A>의 서브타입이다.
- 예를 들어, logNum 함수타입이 logNum = (params :number) => void이고, log 함수의 타입이 log = (params: number | string) => void라고 하자. 이 경우 매개변수 기준으로 logNum 함수의 매개변수가 서브 타입이므로 함수 logNum이 슈퍼타입이 된다.
- 양변
- A가 B의 서브타입이면, 서브타입 -> 슈퍼타입, 슈퍼타입 -> 서브타입 모두 할당 가능.
- 불변
- 타입스크립트의 할당 규칙 : 타입스크립트 설계자들은 쉬운 사용과 안전성 사이의 균형을 중시
- 모든 복합타입의 멤버(객체, 클래스, 배열, 함수, 반환 타입)은 공변
- 함수 매개변수 타입만 반변
- 결론적으로 대부분의 경우 서브타입을 슈퍼타입에 할당할 수 있다.
- 한 가지 유의해야 할 부분은 함수 타입 간 슈퍼/서브 관계를 파악할 때이다.
- 반환타입과 this타입이 슈퍼타입인 타입이 슈퍼타입이 된다.
- 매개변수가 다른 경우, 매개변수가 슈퍼타입인 타입이 서브타입이 된다.
// 함수의 슈퍼타입과 서브타입 관계 이해하기
// 슈퍼타입과 서브타입 만들기
class Animal {}
class Bird extends Animal {
chirp() {}
}
class Crow extends Bird {
caw() {}
}
// 1. 반환 타입(공변)
function clone(f: (b: Bird) => Bird): void {}
function birdToBird(b: Bird): Bird { return new Bird }
// 반환타입이 슈퍼타입인 경우 -> 함수타입도 슈퍼타입으로 판단
function birdToAnimal(b: Bird) : Animal { return new Animal }
// 반환타입이 서브타입인 경우 -> 함수 타입도 서브타입으로 판단
function birdToCrow(b: Bird) : Crow { return new Crow }
clone(birdToBird)
clone(birdToAnimal) // Animal은 Bird에 필요한 chirp 함수가 없음. 슈퍼타입 -> 서브타입에 할당 불가.
clone(birdToCrow) // 서브타입 -> 슈퍼타입 할당 가능
// 2. 매개변수 타입(반변)
// 매개변수는 더 큰 범위를 받아야 항상 필수인 타입을 가지고 있기 때문.
// 매개변수 타입이 슈퍼타입인 경우 -> 함수타입은 서브타입
function animalToBird(b: Animal) : Bird { return new Bird }
// 매개변수 타입이 서브타입인 경우 -> 함수타입은 슈퍼타입
function crowToBird(b: Crow) : Bird { return new Crow }
clone(animalToBird) // 서브타입 -> 슈퍼타입 할당 가능
clone(crowToBird) // Bird에는 Crow에 필요한 caw함수가 없음.
1.3 할당성
- 슈퍼타입과 서브타입의 관계를 파악하면 타입 할당 가능 여부도 알 수 있다.
- A를 B에 할당 가능한지(할당성)를 결정할 때 타입스크립트는 몇 가지 규칙을 확인한다. (열거형 이외 경우)
- 서브타입은 슈퍼타입에 할당할 수 있다.
- A는 any인 경우 -> 자바스크립트와 코드 파악할 때 유용)
- 열거형 타입(enum, const enum)의 경우 다음 조건 중 하나를 만족해야 A타입을 열거형 B에 할당할 수 있다.
- A는 열거형 B의 멤버이다.
- B는 number 타입의 멤버를 최소 한 개 이상 가지고 있으며 A는 number이다.
- 열거형을 사용하지 말자!
1.4 타입 넓히기
- 타입 넓히기는 타입스크립트가 타입을 일반적으로 추론하는 추론 방식을 말한다.
- let이나 var로 변수를 선언하면 그 변수의 타입이 타입 리터럴에서 리터럴 값이 속한 기본 타입으로 넓어진다.
let a1 = 'x' // string
const a2 = 'x' // "x"
let b1 = true // boolean
const b2 = true // true
//타입을 명시하면 타입이 넓혀지지 않게 할 수 있다.
let a3:'x' = 'x' // "x"
let b3:true = true // true
// 자동 확장
const a4 = 'x' // 'x'
let x = a4 // string
- null이나 undefined로 초기화 된 변수는 any 타입으로 넓혀진다
let a = null // any
a = 3 // any
a = 'b' // any
// 선언범위를 벗어나는 경우 좁은 타입 할당
function fn() {
let a = null // any
a = 'abc' // any
}
fn() // a는 string
- const를 사용하면 타입이 넓혀지지 않도록 할 수 있다.
- const를 사용하면 타입 넓히기가 중지되고 멤버들까지 자동으로 readonly가 된다.
초과 프로퍼티 확인
- 한 객체타입을 다른 객체 타입에 할당할 수 있는지를 확인할 때도 타입 넓히기를 이용한다.
- 객체타입과 프로퍼티는 공변 관계이지만, 초과 프로퍼티 확인 기능 덕분에 이를 검출할 수 있다.
- 타입스크립트는 안전성을 확보하기 위해 신선한 객체 리터럴 타입 T를 다른 타입 U에 할당하려는 경우 T가 U에 존재하지 않는 프로퍼티를 추가적으로 가지고 있다면 에러를 발생한다.
- 신선한 객체 리터럴 타입이란 타입스크립트가 객체 리터럴로부터 추론한 타입을 뜻한다. 객체 리터럴이 타입 어서션을 사용하거나 변수에 할당하면 일반 객체 타입으로 넓혀지면서 더 이상 신선하지 않게 된다.
type Options = {
baseURL: string,
cacheSize?: number
tier?:'prod' | 'dev'
}
class API {
constructor(private options: Options) { }
}
// 1. Good !
new API({
baseURL: 'https://api.mysite.com',
tier: 'prod'
})
// 2. 신선한 객체 리터럴 타입 -> 초과 프로퍼티 확인ㅍ-> 에러 발생 O
new API({
baseURL: 'https://api.mysite.com',
tier: 'prod'
someNewValue: 'value' // someNewValue는 Options에 존재하지 않아 에러 발생
})
// 3. 타입 어서션 -> 에러 발생 X
new API({
baseURL: 'https://api.mysite.com',
wrongTier: 'prod'
} as Options)
// 변수에 할당
let wrongOptions = {
baseURL: 'https://api.mysite.com',
wrongTier: 'prod'
}
new API(wrongOptions)
- 이런 규칙들은 모두 외울 필요 없이 타입스크립트 내부 규칙이 이러한 버그를 잡아준다는 사실만 잘 이해하면 된다.
반응형
'FrontEnd > TypeScript' 카테고리의 다른 글
[TypeScript] 비동기 프로그래밍, 동시성과 병렬성 (0) | 2023.06.07 |
---|---|
[TypeScript] 에러 처리 (0) | 2023.06.05 |
[TypeScript] 함수의 타입 (0) | 2023.06.03 |
[TypeScript] 클래스와 인터페이스 (0) | 2023.05.31 |
[TypeScript/타입스크립트 프로그래밍] 타입스크립트 타입 선언 & 종류 (2) | 2023.05.25 |
반응형
1. 타입 간의 관계
1. 1 서브 타입과 슈퍼 타입
- A가 필요한 곳에 B를 사용할 수 있다면 A는 슈퍼 타입, B는 서브 타입이다.
- 예를 들어, 객체를 사용해야 하는 곳에 배열도 사용할 수 있다. 따라서 이 경우 객체는 슈퍼 타입, 배열은 서브 타입이다.
- 모든 것은 any의 서브타입이고, never는 모든 것의 서브타입이다. 즉, 모든 타입을 any에 할당할 수 있고, never는 모든 타입에 할당할 수 있다.
1.2 가변성
- 단순 타입은 이 관계를 파악하기 쉽다. 예) number 는 number | string 유니온의 서브 타입이다.
- 하지만 객체나 함수와 같은 복합 타입의 경우 이 관계를 파악하기가 복잡할 수 있다. 복합 타입의 서브타입 규칙은 언어마다 다르다.
type ExistingUsesr = {
id: number
name: string
}
type NewUser = {
name: string
}
function deleteUser(user: {id?:number, name: string}) {
delete user.id
}
let existingUser:ExistingUsesr = {
id: 12345,
name: 'Ima User'
}
/**
* {id: number | undefined, name: string }은 슈퍼타입
* {id: number,name: string }은 서브타입. (각각의 프로퍼티를 비교)
*
* 서브타입 -> 슈퍼타입에 할당 가능 (공변성)
*/
deleteUser(existingUser)
const id = existingUser.id // 삭제되었지만 number 타입으로 추론하는 문제
형태와 배열 가변성
- 가변성 : 타입과 서브타입의 관계 파악하기
- 불변
- 정확히 T만 할당 가능
- 공변(대부분의 경우)
- A가 B의 서브타입이면, T<A>는 T<B>의 서브타입이다.
- 예를 들어, string은 string | number의 서브타입인 경우를 보자. Array<string>은 Array<string | number>의 서브타입이고, { a: string, b: number }도 { a: string | number, b: number }의 서브타입이 된다.
- 반변
- A가 B의 서브타입이면, T<B>는 T<A>의 서브타입이다.
- 예를 들어, logNum 함수타입이 logNum = (params :number) => void이고, log 함수의 타입이 log = (params: number | string) => void라고 하자. 이 경우 매개변수 기준으로 logNum 함수의 매개변수가 서브 타입이므로 함수 logNum이 슈퍼타입이 된다.
- 양변
- A가 B의 서브타입이면, 서브타입 -> 슈퍼타입, 슈퍼타입 -> 서브타입 모두 할당 가능.
- 불변
- 타입스크립트의 할당 규칙 : 타입스크립트 설계자들은 쉬운 사용과 안전성 사이의 균형을 중시
- 모든 복합타입의 멤버(객체, 클래스, 배열, 함수, 반환 타입)은 공변
- 함수 매개변수 타입만 반변
- 결론적으로 대부분의 경우 서브타입을 슈퍼타입에 할당할 수 있다.
- 한 가지 유의해야 할 부분은 함수 타입 간 슈퍼/서브 관계를 파악할 때이다.
- 반환타입과 this타입이 슈퍼타입인 타입이 슈퍼타입이 된다.
- 매개변수가 다른 경우, 매개변수가 슈퍼타입인 타입이 서브타입이 된다.
// 함수의 슈퍼타입과 서브타입 관계 이해하기
// 슈퍼타입과 서브타입 만들기
class Animal {}
class Bird extends Animal {
chirp() {}
}
class Crow extends Bird {
caw() {}
}
// 1. 반환 타입(공변)
function clone(f: (b: Bird) => Bird): void {}
function birdToBird(b: Bird): Bird { return new Bird }
// 반환타입이 슈퍼타입인 경우 -> 함수타입도 슈퍼타입으로 판단
function birdToAnimal(b: Bird) : Animal { return new Animal }
// 반환타입이 서브타입인 경우 -> 함수 타입도 서브타입으로 판단
function birdToCrow(b: Bird) : Crow { return new Crow }
clone(birdToBird)
clone(birdToAnimal) // Animal은 Bird에 필요한 chirp 함수가 없음. 슈퍼타입 -> 서브타입에 할당 불가.
clone(birdToCrow) // 서브타입 -> 슈퍼타입 할당 가능
// 2. 매개변수 타입(반변)
// 매개변수는 더 큰 범위를 받아야 항상 필수인 타입을 가지고 있기 때문.
// 매개변수 타입이 슈퍼타입인 경우 -> 함수타입은 서브타입
function animalToBird(b: Animal) : Bird { return new Bird }
// 매개변수 타입이 서브타입인 경우 -> 함수타입은 슈퍼타입
function crowToBird(b: Crow) : Bird { return new Crow }
clone(animalToBird) // 서브타입 -> 슈퍼타입 할당 가능
clone(crowToBird) // Bird에는 Crow에 필요한 caw함수가 없음.
1.3 할당성
- 슈퍼타입과 서브타입의 관계를 파악하면 타입 할당 가능 여부도 알 수 있다.
- A를 B에 할당 가능한지(할당성)를 결정할 때 타입스크립트는 몇 가지 규칙을 확인한다. (열거형 이외 경우)
- 서브타입은 슈퍼타입에 할당할 수 있다.
- A는 any인 경우 -> 자바스크립트와 코드 파악할 때 유용)
- 열거형 타입(enum, const enum)의 경우 다음 조건 중 하나를 만족해야 A타입을 열거형 B에 할당할 수 있다.
- A는 열거형 B의 멤버이다.
- B는 number 타입의 멤버를 최소 한 개 이상 가지고 있으며 A는 number이다.
- 열거형을 사용하지 말자!
1.4 타입 넓히기
- 타입 넓히기는 타입스크립트가 타입을 일반적으로 추론하는 추론 방식을 말한다.
- let이나 var로 변수를 선언하면 그 변수의 타입이 타입 리터럴에서 리터럴 값이 속한 기본 타입으로 넓어진다.
let a1 = 'x' // string
const a2 = 'x' // "x"
let b1 = true // boolean
const b2 = true // true
//타입을 명시하면 타입이 넓혀지지 않게 할 수 있다.
let a3:'x' = 'x' // "x"
let b3:true = true // true
// 자동 확장
const a4 = 'x' // 'x'
let x = a4 // string
- null이나 undefined로 초기화 된 변수는 any 타입으로 넓혀진다
let a = null // any
a = 3 // any
a = 'b' // any
// 선언범위를 벗어나는 경우 좁은 타입 할당
function fn() {
let a = null // any
a = 'abc' // any
}
fn() // a는 string
- const를 사용하면 타입이 넓혀지지 않도록 할 수 있다.
- const를 사용하면 타입 넓히기가 중지되고 멤버들까지 자동으로 readonly가 된다.
초과 프로퍼티 확인
- 한 객체타입을 다른 객체 타입에 할당할 수 있는지를 확인할 때도 타입 넓히기를 이용한다.
- 객체타입과 프로퍼티는 공변 관계이지만, 초과 프로퍼티 확인 기능 덕분에 이를 검출할 수 있다.
- 타입스크립트는 안전성을 확보하기 위해 신선한 객체 리터럴 타입 T를 다른 타입 U에 할당하려는 경우 T가 U에 존재하지 않는 프로퍼티를 추가적으로 가지고 있다면 에러를 발생한다.
- 신선한 객체 리터럴 타입이란 타입스크립트가 객체 리터럴로부터 추론한 타입을 뜻한다. 객체 리터럴이 타입 어서션을 사용하거나 변수에 할당하면 일반 객체 타입으로 넓혀지면서 더 이상 신선하지 않게 된다.
type Options = {
baseURL: string,
cacheSize?: number
tier?:'prod' | 'dev'
}
class API {
constructor(private options: Options) { }
}
// 1. Good !
new API({
baseURL: 'https://api.mysite.com',
tier: 'prod'
})
// 2. 신선한 객체 리터럴 타입 -> 초과 프로퍼티 확인ㅍ-> 에러 발생 O
new API({
baseURL: 'https://api.mysite.com',
tier: 'prod'
someNewValue: 'value' // someNewValue는 Options에 존재하지 않아 에러 발생
})
// 3. 타입 어서션 -> 에러 발생 X
new API({
baseURL: 'https://api.mysite.com',
wrongTier: 'prod'
} as Options)
// 변수에 할당
let wrongOptions = {
baseURL: 'https://api.mysite.com',
wrongTier: 'prod'
}
new API(wrongOptions)
- 이런 규칙들은 모두 외울 필요 없이 타입스크립트 내부 규칙이 이러한 버그를 잡아준다는 사실만 잘 이해하면 된다.
반응형
'FrontEnd > TypeScript' 카테고리의 다른 글
[TypeScript] 비동기 프로그래밍, 동시성과 병렬성 (0) | 2023.06.07 |
---|---|
[TypeScript] 에러 처리 (0) | 2023.06.05 |
[TypeScript] 함수의 타입 (0) | 2023.06.03 |
[TypeScript] 클래스와 인터페이스 (0) | 2023.05.31 |
[TypeScript/타입스크립트 프로그래밍] 타입스크립트 타입 선언 & 종류 (2) | 2023.05.25 |