본문 바로가기

개발이야기

TypeScript에서 객체에서 Key로 String을 쓰지 못하는 이유 ( String과 String Literal )

객체의 Key가 String이 아니라고?

TypeScript를 처음 쓰면 익숙하지 않은 불편한 상황들을 만나게 될 때가 종종 생긴다. 평소처럼 코딩하면 생기는 붉은 줄들을 볼 수 있다. 이번에 만난 문제 또한 그렇다. 

 

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때, string타입의 key 사용을 허용하지 않는다는 문구의 등장.

나는 분명 JavaScript의 객체의 키값은 Map이 아닌 경우에는 무조건 String만 된다고 알고있었는데.. 이게 무슨 상황?

 

이런 현상은 단지 이게 TypeScript라서 그렇다는 것이다.

결론적으로 string literal 타입만 허용되는 곳에 string 타입을 사용했기 때문인 것이다.  

 

String과 String Literal의 차이

다음과 같은 TypeScript 코드가 있다. two와 three은 string 타입이 맞지만, one은 "Hello World" 타입이다. Type Script Playground에서 각 변수명에 mouse over하면 타입을 확인할 수 있다.

const one = "Hello World"
let two = "Hello World"
const three: string = "Hello World"

two 변수는 let으로 선언되어 재할당될 수 있을 경우 어떤 문자열이든 넣을 수 있으며 그 경우의 수가 무한대이다. 그렇기 때문에 컴파일러는 이 변수를 string 타입으로 추론한다. 그리고 three 변수는 명시적으로 string 타입으로 선언했으므로 그냥 string 타입이다.

하지만 one의 경우는 조금 이야기가 달라진다. 컴파일러는 이 변수를 string이 아닌 조금 더 좁은 타입(narrowed type)으로 선언한 것으로 추론한다. 이 것을 Literal Narrowing이라고 한다. (참고로 타입 추론은 TypeScript 컴파일러가 제공하는 뛰어난 기능 중 하나이며, 개발자가 명시적으로 타입을 선언해 주지 않을 경우 컴파일러가 할당되는 값을 기준으로 타입을 스스로 결정하는 것을 말한다.)

따라서 one의 타입은 string이 아니라 string타입을 좁혀 만든 string literal type이다. 여기서 "타입을 좁힌다"는 말의 의미는 무한대의 경우의 수를 가질 수 있는 string타입보다 훨씬 구체적인 string의 부분집합, "Hello World"만을 허용하는 타입을 선언했다는 뜻이다.

 

인용: 

https://soopdop.github.io/2020/12/01/index-signatures-in-typescript/

 

TypeScript에서 string key로 객체에 접근하기

TypeScript에서 string key로 객체에 접근하기

soopdop.github.io

 index signature 선언

매번 String Literal로 접근 할 수도 있지만 index signature를 통해 다른 불편한 상황들을 만들지 않고 해결할 수도 있다. 

 

객체의 타입을 정하면서 index signature를 설정해준다. 만드는 방법은 아래와 같다. 

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

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

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

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

이 원리로 나도 트러블 슈팅을 해본다. 

type ObjType = {
  [index: string]: string
  고티수상작: string
  게임 전체 중에서: string
  Y :string
  N :string
}


const filterGuide : ObjType= {
      고티수상작: "게임 전체 중에서",
      "게임 전체 중에서": "고티수상작",
      Y: "N",
      N: "Y",
};

//filter함수에 적용
setBtnValue([
        ...arr.filter((e) => e !== filterGuide[key]),
        target.innerText,
      ]);

아주 잘 동작한다!!