TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때,
string 타입의 key 사용을 허용하지 않는다.
반드시 string literal 타입의 key로 접근하거나,
객체의 타입 정의 시 index signiture를 선언해주어야 한다.
const a = "Hello World" // ⭐ 컴파일러는 이 변수를 string이 아닌 조금 더 좁은 타입으로 선언한 것으로 추론한다.(Literal Narrowing) // ⭐ a의 타입은 string 타입보다 훨씬 구체적인 "Hello World" 타입이다. let b = "Hello World" // ⭐ b변수는 let으로 선언되어 재할당될 수 있을 경우 어떤 문자열이든 넣을 수 있으며 그 경우의 수가 무한대 // ⭐ 그렇기 때문에 컴파일러는 이 변수를 string타입으로 추론한다. const c: string = "Hello World" //c 변수는 명시적으로 string 으로 선언했으므로 string 타입이다.
const obj = { foo: "hello" } let propertyName = "foo" //propertyName는 string 타입(let) console.log(obj[propertyName]) // 💣💣 컴파일 에러! //에러가 발생한 이유는 string literal 타입만 허용되는 곳(객체의 key)에 string 타입을 사용했기 때문
const obj = { foo: "hello", } const propertyName = "foo" console.log(obj[propertyName]) // ok! console.log(obj["foo"]) // ok! // ⭐⭐ 정상 컴파일 //⭐⭐ "foo"와 propertyName 모두 string literal type
타입스크립트가 key의 타입으로 string literal만을 허용하는 이유
마우스 이벤트가 있다고 가정하자.
마우스 이벤트의 종류는 정해져있지만
이벤트 이름을
String 타입
으로 받을 때에는 오타 혹은 유효하지 않은 이벤트 이름으로 인해 발생하는 런타임 에러를 사전에 방지할 수 없다.
<function handleEvent(event:string) {} // 이벤트 이름을 string 타입으로 받는다면 handleEvent("click") handleEvent("clock") // compile error : 오타. 컴파일 타임에 발견할 수 없다. handleEvent("hover") // compile error : 유효하지 않은 이벤트 이름. 컴파일 타임에 발견할 수 없다
반면에,
string literal
타입만을 허용하도록 하면 사전에 오타 혹은 여러가지 에러를 방지할 수 있다.
type EventType = "mouseout" | "mouseover" | "click" //string literal 타입들의 조합 function handleEvent(event:EventType) {} handleEvent("click") handleEvent("hover") //compile error : EventType이라는 타입에 해당 string literal이 정의되지 않았기 떄문
string 타입으로 객체의 프로퍼티에 접근하는 방법은?
index signature
를 사용한다. interface Person { [key: string]: string | number; } const person: Person = {} person['name'] = '공공'; person['나이'] = 19;
동적으로 Object에 property 추가
자바스크립트
key의 type으로
string, number, symbol
를 이용하여동적으로 객체에 프로퍼티를 추가할 수 있다.
만일 key에 그 외의 값이 들어온다면
JS는 런타임 이전에
toString()
을 암묵적으로 호출한다.타입스크립트
key의 type은 무조건
string
혹은 number
이어야 하며,toString()을 암묵적으로 호출하지 않는다.
Index signature의 단점
- 모든 키를 허용한다.
따라서, 객체에 없는 키를 사용하더라도 타입 체크에서 에러가 나지 않는다.
- 특정 키가 필요하지 않다. { }와 같이 빈 객체도 할당이 된다.
- 키마다 다른 타입을 가질 수 없다.
따라서, 값의 타입을 유니온 타입을 사용해 확장시켜야 한다.
- 타입스크립트의 언어 서비스를 제공받을 수 없다.
⇒ 가능하다면 Index signature의 사용을 피하라.
keys를 알고 있다면 Record 타입 혹은 매핑된 타입 사용을 고려해보자.
외부에서 받아오는 것과 같이 keys가 변동될 수 있다면 Index signature의 사용을 고려해보자.
타입의 keys들이 무엇이 될지 아는 경우에는
Index signature
를 사용하는 것보다는 타입을 직접 선언하라.번거롭더라도 타입의 안전성을 위해서는 직접 선언하라.
Record 타입
type Record<K extends keyof any, T> = { [P in K]: T; };
Mapped 타입
이미 선언된 타입의 프로퍼티에 어떤 조작을 가하여 새로운 타입을 만드는 것
type Vec3D = { [k in 'x' | 'y' | 'z']: number }
Mapped 타입은 index signature와 관련이 없다.
in은 for...in의 축약형이라고 이해할 수 있다.
Mapped 타입은 조건부 타입을 이용하여
특정 조건에는 다른 타입을 만들어 줄 수도 있다.

외부로부터 데이터를 받아오는 상황에서
keys가 변동될 가능성이 있다면
Index signature
의 사용을 고려해볼 수 있다. 하지만 keys가 변동될 수 있기 때문에
값의 타입에 null 혹은 undefined를 유니온 타입으로 넣어주는 것을 고려해야 한다.
특정 키가 언제든지 사라질 수 있기 때문이다.
type ExternalType = { [key: string]: string | undefined; }