일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 무한 스크롤
- 리터럴 타입
- 투포인터
- tailwind
- dfs
- map
- 호이스팅
- webpack
- recoil
- app router
- Cypress
- TS
- async/await
- 공변성
- ESlint
- 인터섹션
- Promise
- Jest
- 결정 알고리즘
- useAppDispatch
- SSR
- React
- RTK Query
- 태그된 유니온
- autosize
- CORS
- 타입 좁히기
- 반공변성
- CI/CD
- 이분 검색
- Today
- Total
짧은코딩
타입 넓히기와 좁히기 본문
타입 넓히기
런타임 시 모든 변수는 유일한 값을 가진다. 하지만 타입스크립에서는 코드를 정적 분석하는 시점에서 변수는 가능한 값들의 집합인 타입을 가진다. 즉 타입을 명시하지 않으면 타입 체커는 할당 가능한 값들의 집합을 유추한다. 이러한 과정을 타입 넓히기(widening)이라고 부른다.
타입 넓히기의 문제점
-실행은 되지만 에디터에 오류가 나는 코드
interface Vector3 {
x: number
y: number
z: number
}
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
return vector[axis]
}
let x = 'x'
let vec = { x: 10, y: 20, z: 30 }
getComponent(vec, x) // 이 부분에서 x를 string으로 인식해서 에러가 나게된다.
매개변수 axis에서는 타입을 'x' | 'y' | 'z'로 할당했는데 인자에 x는 string 타입 추론되어서 에러가 난다.
-타입 추론의 모호성
const mixed = ['x', 1];
이렇게 타입을 명시하지 않는 변수가 있다고 하면 타입이 될 수 있는 집합이 엄청 많아진다.
이렇게 많은 후보들이 있다.
에디터는 위 사진처럼 타입을 추론하게된다.
타입스크립트가 타입 추론을 할 수 있지만 정확한 타입을 추론할 수 없는 것이 문제이다.
타입 넓히기 제어 방법
-const 사용하기
const x = "x";
let보다 const를 사용하는 것이 더 좁은 타입을 추론하게 된다. 왜냐하면 const로 변수를 선언하면 재할당 될 수 없어서 위 코드에서는 타입을 "x"로 생각한다. 하지만 const도 객체나 배열에서는 문제가 발생한다.
-배열이나 객체
const v = {
x: 1,
}
v.x = 3 // OK
v.x = '3'
// ~ Type '"3"' is not assignable to type 'number'
v.y = 4
// ~ Property 'y' does not exist on type '{ x: number; }'
v.name = 'Pythagoras'
// ~~~~ Property 'name' does not exist on type '{ x: number; }'
이 코드는 js에서는 정상이다. v의 타입은 구체적으로 {readonly x: 1}이다. 추상적으로는 {x: number}이다. 더 추상적으로는 {[key: string]: number} 혹은 object이다.
객체는 타입 넓히기 과정에서 각 요소를 let으로 할당된 것 처럼 다룬다. 그래서 v의 타입은 {x: number}이다. v.x를 다른 number로 재할당은 가능하다. 그리고 다른 속성을 추가하지 못한다. 따라서 주석을 제외한 아래 3줄에 오류가 발생한다. 이는 객체를 한번에 만들어서 해결할 수 있다.
타입 추론 강도 제어하기
1. 명시적 타입 구문 제공
const v: { x: 1 | 3 | 5 } = {
x: 1,
} // Type is { x: 1 | 3 | 5; }
이런식으로 명시적으로 타입을 적어주면 된다.
2. 타입 체커에 추가적인 문맥을 제공하기
함수의 매개변수로 값을 전달하면 가능하다.
3. const 단언문 사용하기
const v1 = {
x: 1,
y: 2,
} // Type is { x: number; y: number; }
const v2 = {
x: 1 as const,
y: 2,
} // Type is { x: 1; y: number; }
const v3 = {
x: 1,
y: 2,
} as const // Type is { readonly x: 1; readonly y: 2; }
값 뒤에 as const를 사용하면 최대한 좁은 타입으로 추론한다. 배열을 튜플 타입으로 추론할 때도 as const를 사용할 수 있다.
const a1 = [1, 2, 3] // Type is number[]
const a2 = [1, 2, 3] as const // Type is readonly [1, 2, 3]
또 as const를 붙이면 readonly가 붙는 것을 볼 수 있다.
타입 좁히기
타입 넓히기의 반대는 타입 좁히기이다. 타입 좁히기는 넓은 타입부터 좁은 타입으로 진행하는 과정을 말한다.
타입 좁히기 좋은 예시
-null 체크 예시
const el = document.getElementById('foo') // Type is HTMLElement | null
if (el) {
el // Type is HTMLElement
el.innerHTML = 'Party Time'.blink()
} else {
el // Type is null
alert('No element #foo')
}
el이 null이면 첫번째 분기문에서 블록이 실행되지 않아 더 좁은 타입이 되어 작업이 쉬워진다. 타입 체커는 일반적으로 조건문에서 타입 좁히기를 한다. 하지만 타입 별칭이 존재하면 그러지 못할 수도 있다.
const el = document.getElementById('foo') // Type is HTMLElement | null
if (!el) throw new Error('Unable to find #foo')
el // Now type is HTMLElement
el.innerHTML = 'Party Time'.blink()
혹은 이렇게 예외 처리를 하고 타입을 좁힐 수 있다.
-Array.isArray로 좁히기
function contains(text: string, terms: string | string[]) {
const termList = Array.isArray(terms) ? terms : [terms]
termList // Type is string[]
// ...
}
이런식으로도 타입을 좁힐 수 있다.
=> 이런식으로 타입스크립트는 조건문에서 타입을 좁히는 것에 능숙하다. 하지만 타입 좁히기를 잘못할 수 있는 가능성이 있어서 꼼꼼히 봐야한다.
타입 좁히기 잘못된 예시
-유니온 타입에서 null 제외
const el = document.getElementById('foo') // type is HTMLElement | null
if (typeof el === 'object') {
el // Type is HTMLElement | null
}
typeof null이 "object"가 되기 때문에 if 구문에서 null이 제외되지 않았다.
-기본값이 잘못된 사례
function foo(x?: number | string | null) {
if (!x) {
x // Type is string | number | null | undefined
}
}
이 경우에 x가 ""이나 0이면 !x는 true가 되어 if문이 실행된다.
타입 좁히기를 잘 할 수 있는 방법
1. 명시적 태그 붙이기
interface UploadEvent {
type: 'upload'
filename: string
contents: string
}
interface DownloadEvent {
type: 'download'
filename: string
}
type AppEvent = UploadEvent | DownloadEvent
function handleEvent(e: AppEvent) {
switch (e.type) {
case 'download':
e // Type is DownloadEvent
break
case 'upload':
e // Type is UploadEvent
break
}
}
이렇게 태그된 유니온을 사용하면 더 쉽게 타입을 좁힐 수 있다.
2. 사용자 정의 타입 가드
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return 'value' in el
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
el // Type is HTMLInputElement
return el.value
}
el // Type is HTMLElement
return el.textContent
}
반환 타입의 el is HTMLInputElement는 함수 반환이 true인 경우 매개변수의 타입을 좁힐 수 있다.
const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael']
function isDefined<T>(x: T | undefined): x is T {
return x !== undefined
}
const members = ['Janet', 'Michael'].map(who => jackson5.find(n => n === who)).filter(isDefined)
// Type is string[]
혹은 이렇게 타입 가드를 사용해서 undefined를 걸러낼 수 있다.
'TS > 이펙티브 타입스크립트' 카테고리의 다른 글
비동기는 async 사용 (0) | 2023.03.09 |
---|---|
객체 생성하기 (0) | 2023.03.07 |
타입 반복 줄이기(필독) (1) | 2023.02.23 |
type과 interface (0) | 2023.02.21 |
객체 래퍼 타입 지양하기 & 함수 표현식과 타입스크립트 (0) | 2023.02.21 |