[TS] Index Signature
인덱스 시그니처(Index Signature)
{ [ Key : T ] : U }
- T: Key의 타입
- U: Value의 타입
객체의 특정 value에 접근할 때 key를 문자열로 인덱싱하여 참조하는 방법이다. (Key-Value Structure)
Key와 Value의 타입을 정확히 명시해야 하는 경우 사용할 수 있다.
모든 멤버 변수의 규격을 정의하는 기능을 한다.
인덱스 시그니처 선언에서 key의 키워드는 임의로 바꾸어 사용할 수 있고, 동일한 키워드를 여러 개 사용할 수 있다.
Index Signature 선언
type objType = {
[key: string]: number, // Index Signature: key는 string 이고 value는 number 라는 의미
foo: number,
bar: string; // **ERROR**
};
let obj: objType = {
num: 1,
arr: [1, 2, 3] // **ERROR**
};
let mySalary = {
bonus: 200,
pay: 2000,
allowance: 100,
incentive: 100
};
function totalSalary(salary: {[key: string]: number}) {
let total = 0;
for(const key in salary) {
total += salary[key];
}
return total;
};
console.log(totalSalary(mySalary)); // 2400
- totalSalary 함수는 속성의 key가 string 타입이고 value가 number 타입인 salary 객체를 매개변수로 받는다.
- 함수 내부에서 salary 객체의 속성 값(salary[key])에 접근하여 연산을 수행한다.
사용 예
1. 객체 리터럴에 새로운 속성 추가
클래스가 인터페이스를 구현할 때 인터페이스에 정의되지 않은 새로운 속성이 추가되어도 오류를 발생시키지 않는다.
하지만 인터페이스를 타입으로 하는 객체 리터럴에서는 새로운 속성을 추가할 수 없다고 오류를 출력한다.
TypeScript는 기본적으로 인터페이스에 정의되지 않은 속성을 동적으로 할당하는 것을 오류로 정의한다.
interface ButtonInterface {
onInit?():void;
onClick():void;
}
// ButtonInterface 를 구현한 클래스
class ButtonComponent implements ButtonInterface {
// onClick()을 구현하고 있으므로 오류 발생 X
type:string = "button";
disabled:boolean = false;
constructor() {}
onClick() {}
}
// ButtonInterface 을 타입으로 하는 객체 리터럴
const button:ButtonInterface = {
type: "button", // ERROR
disabled: false, // ERROR
onClick() {}
};
이때 인덱스 시그니처를 사용하여 동적으로 추가할 속성을 명시하면 오류를 출력하지 않는다.
[prop:string]: any 추가
interface ButtonInterface {
onInit?():void;
onClick():void;
[prop:string]: any;
}
// ButtonInterface 을 타입으로 하는 객체 리터럴
const button:ButtonInterface = {
type: "button",
disabled: false,
onClick() {}
};
2. String literal 키가 아닌 String 키로 객체에 접근하기
TypeScript는 기본적으로 객체의 프로퍼티에 접근할 때 String literal 타입의 키를 허용한다.
String 키로 객체에 접근할 수 없다.
const obj = {
foo: 'hello'
};
// string key
let propName1 = 'foo';
console.log(obj[propName1]); // ERROR
// string literal key
const propName2 = 'foo';
console.log(obj[propName2]); // hello
console.log(obj['foo']); // hello
Object.keys() 은 string[] 을 반환하므로 JS에서 사용하던 코드를 그대로 사용하면 컴파일 에러가 발생한다.
for (const key of Object.keys(obj)) {
console.log(obj[key]) // error: key가 string 타입
}
해결 방법
- type assertion: as 키워드 사용
const obj = {
foo: 'hello',
};
let propName = 'foo';
console.log(obj[propName as 'foo']);
- index signature
type ObjType = {
[index: string]: string
}
const obj: ObjType = {
foo: "hello",
bar: "world",
}
// string literal
const propertyName1 = "foo"
// string
const propertyName2:string = "bar"
console.log(obj[propertyName1]); // hello
console.log(obj[propertyName2]); // world
3. Number 타입 인덱스 시그니처를 통해 배열 literal 방식으로 프로퍼티 할당하기
number 타입의 인덱스 시그니처를 선언하면 다음과 같이 배열 literal 방식으로 할당할 수 있다.
interface ArrayLikeType {
[key: number]: string;
}
const obj: ArrayLikeType = ["hello", "world"];
/* obj는 아래와 같이 할당된다.
const obj: ArrayLikeType = {
0: "hello",
1: "world"
};
*/
console.log(obj[0], obj[1]); // hello world
String 타입 인덱스 시그니처를 혼용하여 사용하면 배열 literal 방식의 할당은 불가능하다.
interface ArrayLikeType {
[key: number]: string;
[key: string]: string;
}
let obj: ArrayLikeType = {
0: "hello",
foo: "world"
};
let foo = "foo";
console.log(obj[0], obj[foo]); // hello world
obj = ["hello", "world"]; // ERROR
- 오류 내용: Type 'string[]' is not assignable to type 'ArrayLikeType'. Index signature for type 'string' is missing in type 'string[]'.
- foo는 string 타입이지만 인덱스 시그니처를 사용하여 객체의 프로퍼티에 접근할 수 있다.
주의 사항
1. key의 타입은 string, number, symbol, Template literal 타입만 가능하다
type userInfoType = "name" | "age" | "address"; // string literal + union
type userType = {
[key: string]: string | number | boolean;
[key: userInfoType]: string; // error: An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
};
const user: userType = {
name: '홍길동',
age: 20,
isMale: true
};
- 인덱스 시그니처의 타입으로 literal 이나 generic 타입은 사용할 수 없다.
- 위와 같은 상황에서 IDE는 Mapped type을 권장하고 있다.
Index Signature와 Mapped Type
- Mapped type은 인덱스 시그니처와는 관련이 없다.
// Mapped Type을 이용한 Index Signature 선언
type userInfoType = 'name' | 'age' | 'address';
type userType = {
[key in userInfoType]: string; // in 키워드 사용
};
/* 위 코드는 아래 코드와 완전히 동일하므로 Index Signature와는 무관하다.
type userType = {
name: string,
age: string,
address: string
};
*/
const user: userType = {
name: '홍길동',
age: '20',
address: '서울',
gender: 'male' // error
};
2. Key는 고유한 값으로 동일한 이름의 프로퍼티를 여러 개 가질 수 없다.
또한 동일한 타입의 인덱스 시그니처를 여러 개 선언할 수 없다. (키워드 이름과 무관)
type objType = {
[key: string]: string | number,
[field: string]: number, // error: Duplicate index signature for type 'string'
[index: number]: string,
length: number
};
const obj: objType = {
key: "string",
key: 1 // error: An object literal cannot have multiple properties with the same name.
}
- length는 일반 프로퍼티. key가 string 이므로 value로 string 또는 number 값을 참조한다.
- obj[key] = "string"? 1? (error)
3. 사용자 정의 타입을 인덱스 시그니처로만 정의하면 빈 객체를 할당해도 타입 에러가 발생하지 않는다.
type objType = {
[key: string]: number | string,
[index: number]: string
};
const obj: objType = {}
console.log(obj['prop']); // undefined
런타임에 객체의 속성(property)를 알 수 없는 경우에만 제한적으로 사용하는 것을 권장