아이템20 - 다른 타입에는 다른 변수 사용하기
let id = "12-34-56"
fetchProduct(id) // string 으로 사용
id = 123456;
fetchProductBySerialNumber(id) //number 으로 사용
// '123456'형식은 'string' 형식에 할당할 수 없습니다.
타입스크립트는 '12-34-56' 값을 보고 string 으로 추론했기 때문에 number 형식은 할당할 수 없습니다.
여기서 '변수의 값은 바뀔 수 있지만 타입은 보통 바뀌지 않는다'는 중요한 관점을 알 수 있습니다.
const id = '12-34-56'
fetchProduct(id)
const serial = 123456 // OK
fetchProductBySerialNumber(serial) // OK
다른 타입에는 별도의 변수를 사용하는 것이 좋습니다
- 변수명을 더 구체적으로 지을수 있습니다.
- 타입추론을 향상시키며,타입 구문이 불필요해집니다.
- 타입이 간결해집니다.(string|number 대신 string 과 number를 사용)
- let 대신 const 로 변수를 선언합니다. const 로 선언하면 타입체커가 타입을 추론하기에 좋습니다.
아이템21 - 타입 넓히기
타입스크립트가 작성된 코드를 체크하는 정적 분석 시점에, 변수는 '가능한'값들의 집합인 타입을 가집니다.
상수를 사용하여 변수를 초기화할 때 타입을 명시하지 않으면 지정된 단일 값을 가지고 할당 가능한 값들의 집합을 유츄해야된다는 뜻입니다.
타입스크립트에서는 이러한 과정을 '넓히기(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)
// ~ Argument of type 'string' is not assignable to
// parameter of type '"x" | "y" | "z"'
변수 x는 타입 할당 시점에 string 으로 추론됐기때문에 string 타입은 "x"|"y"|"z" 에 할당이 불가능하므로 에러가 발생합니다.
const mixed = ['x',1];
// mixed의 추론가능한 타입들
/*
('x'|1)[]
['x',1]
[string,number]
(string|number)[]
[any,any]
...
*/
타입스크립트는 작성자의 의도를 추측하여 타입을 추론합니다 하지만 아무리 타입스크립트가 영리하더라도 작성자의 의도를 완전히 파악할 수는 없습니다.
타입스크립트의 넓히기 과정을 제어 할수있는 방법들이 있습니다.
첫번째 , let 대신 const 사용하기
interface Vector3 {
x: number
y: number
z: number
}
function getComponent(vector: Vector3, axis: 'x' | 'y' | 'z') {
return vector[axis]
}
const x = 'x' // type is "x"
let vec = { x: 10, y: 20, z: 30 }
getComponent(vec, x) // OK
let 대신 const 를 사용하면 더 좁은 타입이 됩니다. 변수 x는 재할당 될 수 없으므로 의심의 여지없이 좁은타입('x')으로 추론할 수 있습니다.
그리고 'x'|'y'|'z' 에 할당 가능하므로 타입 체커를 통과합니다.
그러나 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; }'
타입스크립트의 넓히기 알고리즘은 각 요소를 let으로 할당된것 처럼 다룹니다. 따라서 v의 타입은 {x:number} 가 됩니다.
덕분에 v.x 를통해 숫자를 재할당 할수있지만 string 형식을 재할당하려고 하면 오류가 발생합니다. 또한 속성도 추가하지 못합니다.
(따라서 객체는 한번에 만들어야 합니다 아이템26)
두번째, 타입 체커에 추가적인 문맥을 제공하기(아이템26)
세번째 , 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 를 사용하면 최대한 좁은 타입으로 추론합니다.
아이템22 - 타입 좁히기
타입 좁히기는 타입스크립트가 넓은 타입으로부터 좁은 타입으로 진행되는 과정을 말합니다.
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')
}
instanceof
function contains(text: string, search: string | RegExp) {
if (search instanceof RegExp) {
search // Type is RegExp
return !!search.exec(text)
}
search // Type is string
return text.includes(search)
}
Array.isArray
function contains(text: string, terms: string | string[]) {
const termList = Array.isArray(terms) ? terms : [terms]
termList // Type is string[]
// ...
}
속성 체크
interface A {
a: number
}
interface B {
b: number
}
function pickAB(ab: A | B) {
if ('a' in ab) {
ab // Type is A
} else {
ab // Type is B
}
ab // Type is A | B
}
태그된 유니온
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
}
}
커스텀 타입 가드
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
}
------------------------------------------------------------------------------------------
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[]
//isDefined 함수는 제네릭 타입 T를 입력받아, T가 undefined가 아니면 true를 반환합니다. 이 함수는 TypeScript에서 undefined 타입을 제거하고, T타입으로 추론되는 값만을 허용하도록 하는 역할을 합니다.
타입 좁히기 잘못된 예제
const el = document.getElementById('foo') // type is HTMLElement | null
if (typeof el === 'object') {
el // Type is HTMLElement | null
}
자바스크립트에서 typeof null 이 object 이기 때문에 if 구문에서 null 이 제외되지 않았습니다.
'Typescript' 카테고리의 다른 글
Effective Typescript - 아이템 24 (0) | 2023.04.18 |
---|---|
Effective Typescript - 아이템23 (0) | 2023.04.18 |
Effective Typescript - 아이템19 (0) | 2023.04.12 |
Effective Typescript - 아이템18 (0) | 2023.04.12 |
Effective Typescript - 아이템17 (0) | 2023.04.12 |