프로젝트 진행 중, 코드를 리팩토링하면서 아래와 같이 map으로 정의하여 tailwind 식의 css를 부여하려했다. 하지만 이 과정에서 오류가 발생했고, TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때, string 타입의 key 사용을 허용하지 않는다것을 알 수 있었다!
따라서, 이 오류를 해결하기 위해 객체의 <key, value> 형식에서 key와 value의 타입을 정확하게 명시하여 사용하는 인덱스 시그니처
라는 것을 설명해보겠다!
const headingTextSizeMap = {
sm: 'text-sm',
md: 'text-base'
...
}
const HeadingText = ({ size = 'sm', children, className, ...rest }: Props) => {
const fontSize = headingTextSizeMap[size]; // complie error!
return (
<div className={['font-black', fontSize, className].join(' ')} {...rest}>
{children}
</div>
);
};
위 코드를 컴파일할 때 에러가 발생한 이유는 string literal
타입만 허용되는 곳에 string
타입을 사용했기 때문이다.
string key로 객체에 접근하지 못하는 것은 여러모로 불편하다. 아래와 같이 Object.keys() 에서 리턴되는 값은 string[]
이기 때문에 JavaScript에서 사용하던 코드를 그대로 사용하면 컴파일 에러가 발생한다.
for (const key of Object.keys(obj)) {
console.log(obj[key]) // compile error! key가 string타입이다.
}
따라서, 반드시 string literal
타입의 key로 접근하거나 객체의 타입 정의 시, 인덱스 시그니처
를 선언해주어야한다.
그럼 string
과 string literal
이 둘의 차이점은 무엇일까?
const a = "Hello World"
let b = "Hello World"
const c: string = "Hello World"
위와 같은 TypeScript 코드가 있다. b와 c은 string
타입이 맞지만, a는"Hello World"
타입이라는 것을 알 수 있다.
Literal Narrowing
)으로 선언한 것으로 추론한다.string literal type
이다. 여기서 "타입을 좁힌다"는 말의 의미는 무한대의 경우의 수를 가질 수 있는 string 타입보다 훨씬 구체적인 string의 부분집합, "Hello World"만을 허용하는 타입을 선언했다는 뜻이다.따라서, 아래와 같이 명시적으로 literal type
을 선언하면 let으로 선언된 변수도 "Hellow World" 타입만을 허용하도록 만들 수도 있다.
type HelloWorldType = "Hello World" // literal type
let a: HelloWorldType = "Hello World" // ok
a = "hahaha" // compile error: "hahaha"는 "Hello World"가 아니기 때문이다.
let obj {
월급 : 200,
보너스 : 200,
인센티브 : 100,
복지포인트 100
}
위와 같이 급여와 관련된 객체가 있고, 객체 내부에 존재하는 속성의 값을 모두 합산해야하는 경우라면 인덱스 시그니처
를 사용해야한다.
function totalPay (payment : 무슨타입?){
let total = 0;
for (const key in payment){
total += payment[key];
}
return total;
}
totalPay() 함수의 인자 payment는 key가
string
이고 value가number
타입인 객체만 허용해야 한다. key가 string 타입이 아닌 경우 객체의 속성을 접근하는데 문제가 발생하며, value가 number 타입이 아닌 경우 총 금액을 계산하는데 문제가 발생하기 때문이다.
따라서, 아래와 같이 인덱스 시그니처
를 사용하여 객체의 타입을 정의해준다.
function totalPay (payment : {[key : string] : number}){
let total = 0;
for (const key in payment){
total += payment[key];
}
return total;
}
▶ Key와 Value의 타입이 string
type userType = {
[key: string]: string;
}
let user: userType = {
'홍길동': '사람',
'둘리': '공룡'
}
▶ Key의 타입은 string
이며 Value의 타입은 string
, number
, boolean
type userType = {
[key: string]: string | number | boolean;
}
let user: userType = {
'name': '홍길동',
'age': 20,
'man': true
}
위에서 배운 내용을 토대로 오류가 발생한 코드를 인덱스 시그니처
를 사용하여 해결할 수 있었다.
type ObjType = {
[index: string]: string; // 인덱스 시그니처, index는 정해진 키워드가 아니라 개발자가 마음대로 지정할 수 있다.
};
const headingTextSizeMap: ObjType = {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
};
const HeadingText = ({ size = 'lg', children, className, ...rest }: Props) => {
const fontSize = headingTextSizeMap[size]; // ok!
return (
<div className={['font-black', fontSize, className].join(' ')} {...rest}>
{children}
</div>
);
};