Index Signature

huewilliams·2021년 8월 29일
0
post-thumbnail

배경

https://velog.io/@huewilliams/unknown-Type 에서 언급했듯, Slate 라이브러리 학습 도중 다음 예제 코드에서 [key: string]: unknown 부분이 이해되지 않아 공부하고 정리하게 되었다.

interface BaseProps {
  className: string
  [key: string]: unknown
}

처음에는 Array Destructure 문법으로 생각했었는데, 해당 문법인 경우엔 [a,b,c]: [number, number, number]와 같이 사용하기 때문에 위 코드와 선언과 사용에서 차이가 있었다. 그래서 검색 끝에 Index Signature 문법이라는 것을 알게 되었다.

정리

string과 stringLiteral로 객체 접근

const obj = {
  prop: "value"
}

let property = "prop";
console.log(obj[property]); // Error!

JavaScript와 달리 TypeScript는 객체의 프로퍼티를 읽을 때 string 타입의 key 사용을 허용하지 않는다. 따라서 위 코드에서 컴파일 오류가 발생한다.

const a = "Hello World";

let b = "Hello World";

const c: string = "Hello world";

위 예제에서 컴파일러는 a를 string이 아닌 조금 더 좁은 타입으로 선언한 것으로 추론한다(Literal Narrowing). a의 타입은 string 타입보다 구체적인 "Hello World" 타입이다.

b 변수는 let으로 선언되어 어떤 문자열로든 재할당 될 수 있어 컴파일러는 이 변수를 string 타입으로 추론한다.

c 변수는 명시적으로 string 타입으로 선언했으므로 string 타입이다.

이제 처음의 코드를 string literal을 사용하여 정상적으로 컴파일 할 수 있다.

const obj = {
  prop: "value"
}

const property = "prop";
console.log(obj[property]); // OK
console.log(obj["prop"]); // OK

String Literal의 유용함

string literal 타입은 열거형 타입처럼 사용할 때 매우 유용하다. 마우스 이벤트를 처리하는 함수가 있을 때, 이벤트 이름을 string으로 받는다면 오타 혹은 유효하지 않은 이벤트 이름으로 인해 발생하는 런타임에러를 사전에 방지할 수 없다.

function handleEvent(event: string) {}
handleEvent("click");
// 오타나 유효하지 않은 이름이지만 컴파일 타임에 걸러낼 수 없다.
handleEvent("clock"); // Runtime Error!

다음과 같이 string literal 타입 조합을 이용하여 오타를 컴파일 타임에 알 수 있고 IDE의 suggestion 기능도 사용할 수 있다.

type EventType = "mouseout" | "mouseover" | "click";
function handleEvent(evnet: EventType) {}
handleEvent("click");
handleEvent("clock"); // Compile Error!

Index Signature 선언

위에서는 string 키로 객체에 접근할 수 없어서 string literal 키로 대체하여 사용했다. 그러나 Index Signature를 사용하면 string 키로 객체에 접근할 수 있다.

type ObjType = {
  [index: string]: string;
  foo: string;
  bar: string;
}

const obj: ObjType = {
  foo: "hello",
  world: "world",
}

const propertyName1 = "foo";
const propertyName2: string = "foo";

console.log(obj[propertyName1]); // ok
console.log(obj[propertyName2]); // ok

Index Signature 선언에서 사용된 index는 Typescript에서 아무 의미가 없으며 가독성을 위해 넣은 내용이다. index가 아닌 어떤 값이 와도 상관없다.

Mapped Type 사용

type AllowedKeys = "hello" | "world";

type ObjType = {
  [key in AllowedKeys]: number // OK
}

위와 같이 String 대신 narrowed 타입으로 index signature를 선언하면 오류가 발생한다.
이미 선언된 타입의 프로퍼티에 어떤 조작을 가하여 새로운 타입을 만드는 것을 mapped types라고 한다.

type AllowedKeys = "hello" | "world";

type ObjType = {
  [key in AllowedKeys]: number // OK
}

위 코드는 얼핏 Index Signature 선언과 닮아 보이지만 이 코드는 다음 코드와 완전히 동일하므로 Index Signature와는 전현 관련 없는 코드이다.

type ObjType = {
  hello: number;
  world: number;
}

Reference

https://radlohead.gitbook.io/typescript-deep-dive/type-system/index-signatures
https://soopdop.github.io/2020/12/01/index-signatures-in-typescript/
https://velog.io/@yyeonjju/TypeScript-Index-Signature-string-key%EB%A1%9C-%EA%B0%9D%EC%B2%B4%EC%97%90-%EC%A0%91%EA%B7%BC%ED%95%98%EA%B8%B0

0개의 댓글