Index Signature

조민호·2023년 3월 3일
1

Index Signature의 사용 방법

인덱스 시그니처는 객체가 <key, vlaue>형식이며 key와 value의 타입을 정확하게 명시해야하는 경우 사용합니다


인덱스 시그니쳐(Index Signature)는 {[key : T] : U}형식으로

  • 객체가 여러개의 Key를 가질 수 있으며
  • Key와 매핑되는 value를 가지는 경우 사용합니다
💡 저기서 key라는 키워는 정해진게 아니므로 맘대로 바꿔서 사용할 수 있습니다

인덱스 시그니쳐는 속성에 타입을 선언하는 구문과 유사하지만

속성 이름 대신 대괄호 안에 key타입을 작성합니다


  • key와 value의 타입이 string인 경우
    type userType = {
    	[key : string] : string
    }
    
    let user : userType = {
    	'공격수':'손흥민'
      '수비수':'김민재'
    }


  • key의 타입이 string
  • value의 타입이 string or number or boolean 인 경우
    type userType = {
      [key: string]: string | number | boolean;
    }
    
    let user : userType = {
    	'이름' : '홍길동'
      '나이' : 38
    	'졸업' : true
    }
  • key의 타입을 Template literal로 선언 하는 경우

    키값으로 단순한 타입뿐만이 아니라 어느 값을 사용할 수 있을지 정해줄 수 있는 유용한 로직입니다

    type userInfoType = "name" | "age" | "address";
    
    type userType = {
    
    	**// in 키워드를 사용해야 합니다!**
      [key in userInfoType]: string;
    };
    
    let user: userType = {
      'name': '일론 머스크',
      'age': '38',
      'address': '라스베가스'
    };



    💡 이 부분은 index signature이라는 사람도 있고 mapped type이라는 사람도 있습니다
    왜냐하면 위의 코드는 아래와 완전이 동일한 것이기 때문입니다 그러므로 index signature와는 전혀 관련 없는 코드라고 합니다
type userType = {
  name : string,
  age: string,
  address : string
}

그리고 여기서 사용된 Mapped type에서의 in 키워드는 for...in
의 축약형이라고 이해하는 것이 좋습니다

userInfoType의 각 요소를 하나씩 가져오고 그 것을 이용해 새로운 프로퍼티들을 만드는 것입니다

in키워드는 JS에서 객체가 특정 프로퍼티를 가지는지 체크하는 것과,
TS에서 Mapped Type에 사용되는 in키워드가 있는데
여기서 사용된 in은 Mapped Type의 in 키워드인 것입니다



  • index signture의 number 타입으로 선언하면 다음과 같이 배열 literal 방식으로 할당도 가능합니다
    interface ArrayLikeType {
      [key: number]: string
    }
    const obj: ArrayLikeType = ["hello", "world"]
    console.log(obj[0], obj[1]) // "hello" "world"


주의 사항

💡 key 타입은 string, number, symbol, Template literal타입 외에는 사용할 수 없습니다
type userType = {
  [key: string | **boolean**]: string; // boolean은 불가능합니다
}

💡 key는 고유한 값이므로 동일한 key를 여러개 가질 수 없습니다
type userType = {
  [key: string]: string;
};

let user: userType = {
  이름: "배트맨",
  이름: "슈퍼맨",  // 중복된 key
  나이: "40"
};

💡 index signature가 선언된 경우 모든 맴버 변수가 이것을 따라야 합니다.
  • 아래의 방식대로 index signature를 선언하면 이 형식에 맞춰서 key : value쌍을 제한없이 만들 수 있습니다
    type ObjType = {
      [index: string]: string
    }
    
    const obj: ObjType = {
      foo: "hello",
      bar: "world",
    }

  • 그리고 index signature는 동시에 모든 멤버변수들을 규격을 정의하는 기능도 합니다
    type ObjType = {
    	// ObjType 에 존재하는 모든 키는 string 타입
    	// ObjType 에 존재하는 모든 값은 string 타입이 됩니다
      [index: string]: string
    	foo: string, // foo라는 키 또한 string타입이 됩니다 (string literal 아님)
      bar: string  // bar라는 키 또한 string타입이 됩니다 (string literal 아님)
    }
    
    const obj: ObjType = {
      foo: "hello",
      bar: "world",
    }
    index signature는 키와 값 모두 다 string타입이어야 합니다
    만약 다른 타입을 사용하면 에러가 발생합니다
    type ObjType = {
      [key: string]: string
      foo: string
      bar: number // error! Property 'bar' of type 'number' is not assignable to string index type 'string'.
    }

    위처럼 선언했다고 해서 foo 와 bar 말고 다른 프로퍼티를 선언할 수 없는 것도 아닙니다
    키는 string이고 값은 string인 타입의 프로퍼티를 계속해서 만들 수 있니다



간단한 예시)

아래와 같이 급여와 관련된 객체가 있고, 객체 내부에 존재하는 속성의 값을 모두 합산해야하는 totalPay() 함수가 있습니다

let obj {
		월급 : 200,
    보너스 : 200,
    인센티브 : 100,
    복지포인트 100
}
													 **// 인덱스 시그니처**
function totalPay (payment : {[key : string] : number}){
	let total = 0;
  	for (const key in payment){
    	total += payment[key];
    }
  return total
}

totalPay()함수의 인자 payment는 key가 string이고 value가 number 타입인 객체만 허용해야 합니다

  • 급여 종류를 나타내는 key가 string타입이 아닌 경우
  • 금액을 나타내는 Value가 number타입이 아닌 경우

총 급여 계산을 하지 못하므로 에러를 발생시키는 것입니다






왜 Index Signature를 사용할까?


예시 1)

클래스가 인터페이스를 구현할 때 인터페이스에 정의되지 않은 새로운 속성이 추가되어도 오류를 발생시키지 않습니다.

interface ButtonInterface {
  onInit?():void; 
  onClick():void;
}

class ButtonComponent implements ButtonInterface {

	// onClick을 구현하고 있으므로 전혀 문제없이 사용가능
  type:string = 'button';
  disabled:boolean = false;

  constructor() {}

  onClick() { console.log('버튼 클릭') }

}

하지만 인터페이스를 타입으로 하는 객체 리터럴의 경우는 다릅니다.

새로운 속성을 추가할 수 없다고 오류를 출력합니다.

interface ButtonInterface {
  onInit?():void;
  onClick():void;
}

const button:ButtonInterface = {
  type: 'button', // 에러
  disabled: false, // 에러
  onClick() { console.log('버튼 클릭') }
};

오류가 발생한 이유는 인터페이스에 정의되지 않은 동적 타입이 할당되는 것을 TypeScript는 기본적으로 오류로 보기 때문입니다.



이 문제를 해결하기 위해서 2가지의 방법을 사용할 수 있습니다

  1. tsconfig.json 설정 파일의 noImlictAny 옵션 값을 false로 변경하는 것입니다.

    {
      "compilerOptions": {
        ...,
        "noImplicitAny": false, 
        ...
      }
    }

    하지만 TypeScript 프로그래밍 시, 암묵적으로 any 타입을 사용하는 것을 피하고자 한다면 좋은 방법이 될 수 없습니다.

  1. 바로 인덱스 시그니처를 사용하는 것입니다

    이 방법은 객체의 새로운 추가 속성을 명시적으로 any 타입으로 설정한 것으로 오류를 출력하지 않습니다.

    interface ButtonInterface {
      onInit?():void;
      onClick():void;
      // 인덱스 시그니처
      [prop:string]: any;
    }
    
    const button:ButtonInterface = {
      type: 'button',
      disabled: false,
      onClick() { console.log('버튼 클릭') }
    };


예시 2)

TypeScript는 기본적으로 객체의 프로퍼티를 읽을 때 string타입의 key사용을 허용하지 않으며
string literal 타입을 허용합니다

const obj = {
  foo: 'hello',
};

let propertyName1 = 'foo';
console.log(obj[propertyName1]); // error

const propertyName2 = 'foo';
console.log(obj[propertyName2]); // hello
console.log(obj['foo']); // hello

이처럼 string키로 객체에 접근하지 못하는 것은 매우 불편합니다

Object.keys() 에서 리턴되는 값은 string[] 이기 때문에 JS에서 사용하던 코드를 그대로 사용하면 컴파일 에러가 발생합니다

for (const key of Object.keys(obj)) {
  console.log(obj[key]) // error(key가 string타입)
}

이와 같은 문제를 해결하기 위해서 2가지의 방법을 사용할 수 있습니다

  1. type assertion

    const obj = {
      foo: 'hello',
    };
    
    let propertyName1 = 'foo';
    console.log(obj[propertyName1 as 'foo']);
  1. 바로 인덱스 시그니처를 사용하는 것입니다

    type ObjType = {
      [index: string]: string
    	foo: string,
      bar: string
    }
    
    const obj: ObjType = {
      foo: "hello",
      bar: "world",
    }
    
    const propertyName1 = "foo"
    const propertyName2: string = "bar"
    
    console.log(obj[propertyName1]) // ok
    console.log(obj[propertyName2]) // ok

    string타입과 literal type 모두를 사용해서 obj 에 접근할 수 있게 됐습니다

    위처럼 index signature가 선언된 경우 모든 맴버변수가 이것을 따라야 합니다. 그렇지 않으면 다음과 같이 에러가 발생합니다

    type ObjType = {
      [key: string]: string
      foo: string
      bar: number // error! Property 'bar' of type 'number' is not assignable to string index type 'string'.
    }


출처 :

인덱스 시그니처 속성

[TypeScript] 인덱스 시그니처

profile
할 수 있다

0개의 댓글