TypeScript | Index Signature - string key로 객체에 접근하기

TypeScript | Index Signature

  • JSX 파일을 TSX확장자로 바꾸어 작업을 하면서 객체를 활용하고 프로퍼티에 접근, 프로퍼티를 추가하는데에 자바스크립트와 타입스크립트 간에 차이가 있다는 것을 새롭게 알게되어 아래에 정리해보고자 한다.
  • 먼저, 타입스크립트 오류 메시지 중 읽어봐도 감이 안잡혔던 오류를 해결한 방법을 통해 객체 활용 시 자바스크립트와 타입스크립트의 차이점에 대해 말하면 좋을 것 같다.
오류 메시지
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'ILanguageSvgObj'. 
No index signature with a parameter of type 'string' was found on type 'ILanguageSvgObj'.  TS7053  

🍊 string 과 string literal

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


🍊 String Literal의 유용함

String Literal타입은 열거형 타입처럼 사용할 때 매우 유용하다.
마우스 이벤트가 있다고 가정하고, 이벤트 종류는 정해져있지만 이벤트 이름을 String 타입으로 받을 때는 오타 혹은 유효하지 않은 이벤트 이름으로 인해 발생하는 런타임 에러를 사전에 방지 할 수 없다.

<function handleEvent(event:string) {} // 이벤트 이름을 string 타입으로 받는다면
handleEvent("clock") 
handleEvent("click")  //compile error : 오타. 컴파일 타임에 발견할 수 없다.
handleEvent("hover")  //compile error : 유효하지 않은 이벤트 이름. 컴파일 타임에 발견할 수 없다

반면, string literal 타입 조합만을 허용하도록 하면 사전에 오타 혹은 여러가지 에러를 방지할 수 있다.

type EventType = "mouseout" | "mouseover" | "click" //string literal 타입들의 조합
function handleEvent(event:EventType) {}  //string literal 타입들의 조합을 이벤트(매개변수)의 타입으로 선언한다.
handleEvent("click")
handleEvent("hover") //compile error : EventType이라는 타입에 해당 string literal이 정의되지 않았기 떄문


🍊 String 키를 이용한 객체 접근

🍀그렇다면 string literal 타입이 아닌 string 타입으로 객체에 접근할 수 있는 방법은 없을까?

당연히 방법은 있다~!

🍀index signature를 선언하는 방법

객체의 타입을 지정할 때 아래 처럼 index signature를 한 줄 추가해주면
객체의 프로퍼티를 읽을 때 key값에 string literal 타입이 아닌 string 타입이 들어가도 컴파일이 가능하다.

const Svg = ({ name, hoverIcon }: Props) => {

  interface ISvgObj {
      [key: string]: () => any;  // ⭐⭐⭐ index signature
    }

  const SvgObj: ISvgObj = {
      C: () => (
       <svg
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 128 128"
        width="current"
        height="current"
       >
         <path
          fill="current"
          d="M117.5 33.5l.3-.2c-.6-1.1-1.5-2.1-2.4-2.6l-48.3-27.8c-.8-.5-1.9-.7-3.1-.7-1.2 0-2.3.3-3.1.7l-48 27.9c-1.7 1-2.9 3.5-2.9 5.4v55.7c0 1.1.2 2.3.9 3.4l-.2.1c.5.8 1.2 1.5 1.9 1.9l48.2 27.9c.8.5 1.9.7 3.1.7 1.2 0 2.3-.3 3.1-.7l48-27.9c1.7-1 2.9-3.5 2.9-5.4v-55.8c.1-.8 0-1.7-.4-2.6zm-53.5 55c9.1 0 17.1-5 21.3-12.4l12.9 7.6c-6.8 11.8-19.6 19.8-34.2 19.8-21.8 0-39.5-17.7-39.5-39.5s17.7-39.5 39.5-39.5c14.7 0 27.5 8.1 34.3 20l-13 7.5c-4.2-7.5-12.2-12.5-21.3-12.5-13.5 0-24.5 11-24.5 24.5s11 24.5 24.5 24.5z"
         />
       </svg>
     )
  }

 return (
  <SvgContainer hover={name === hoverIcon}>
      {SvgObj[name]()}  
    // ⭐⭐⭐ name(string 타입)이라는 변수를 key값으로 객체의 프로퍼티에 접근
    // ⭐⭐⭐ 객체의 타입을 정의할 때 index signature를 선언해주었기 때문에 string 타입이어도 컴파일이 가능
  </SvgContainer>
 )

}

🍀 드디어 string타입과 literal type 모두를 사용해서 obj에 접근할 수 있게 되었다 🥰






추가적으로 객체에 프로퍼티를 추가하는 것에 대한 이야기를 잠깐하고 마무리하겠다.

동적으로 Object에 property 추가하기

🍊 JavaScript 에서의 프로퍼티 추가

  • key의 type으로 number , string , symbol 을 이용하여 dynamic 하게 객체에 property를 추가할 수 있다.
  • 스트링(‘ ’) / 변수명 / 숫자 로 접근 할 수 있고, 점 표기범 (dot-notation) 이나 괄호 표기법 (bracket-notation) 둘다 지원한다. ( 단, number일 경우에는 괄호 표기법은 지원하지 않는다)
  • obj[age] = 27 에서의 age 처럼 key에 number도 string도 아닌 값이 들어온다면 JS는 런타임 이전에 toString()을 암묵적으로 호출해서 obj['age'] = 27 로 만든다.


🍊 TypeScript 에서의 프로퍼티 추가

  • ⭐ 반면, TS에서는 toString()을 암묵적으로 호출해주지 않는다. 즉, 자바스크립트와 다르게 key type무조건 string 혹은 number이어야 하는 것이다.
  • ⭐ 또한 객체가 어떤 key type을 받을 것인지도 정해줘야한다.
const person = {};
person[name] = 'Yeonju' // ⭐ error : js와 다르게 name에 toString을 암묵적으로 호출하지 않는다.
person["name"] = 'Yeonju'  // ⭐ error : 어떤 key Type을 Object가 받을 것인지 명시해줘야한다.
const foo : string = 'foo';
person[foo] = 'hello'  // ⭐ error : 어떤 key Type을 Object가 받을 것인지 명시해줘야한다.

🍀 해결방안 : index signature를 선언

  interface IPerson {
    [key: string]: string;
  }

  const person: IPerson = {};

  person['name'] = 'Yeonju';  //ok!!
  const foo: string = 'foo';
  person[foo] = 'hello';      //ok!!

참고 글
https://velog.io/@dongdong98/index-Signature-동적으로-Object에-property-추가하기
https://soopdop.github.io/2020/12/01/index-signatures-in-typescript/

좋은 웹페이지 즐겨찾기