인덱스 시그니처는 객체가 <key, vlaue>형식이며 key와 value의 타입을 정확하게 명시해야하는 경우 사용합니다
인덱스 시그니쳐(Index Signature)는 {[key : T] : U}
형식으로
여러개의 Key
를 가질 수 있으며인덱스 시그니쳐는 속성에 타입을 선언하는 구문과 유사하지만
속성 이름 대신 대괄호 안에 key타입을 작성합니다
type userType = {
[key : string] : string
}
let user : userType = {
'공격수':'손흥민'
'수비수':'김민재'
}
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': '라스베가스'
};
type userType = {
name : string,
age: string,
address : string
}
그리고 여기서 사용된 Mapped type에서의 in 키워드
는 for...in
의 축약형이라고 이해하는 것이 좋습니다
userInfoType의 각 요소를 하나씩 가져오고 그 것을 이용해 새로운 프로퍼티들을 만드는 것입니다
in키워드는 JS에서 객체가 특정 프로퍼티를 가지는지 체크하는 것과,
TS에서 Mapped Type에 사용되는 in키워드가 있는데
여기서 사용된 in은 Mapped Type의 in 키워드인 것입니다
interface ArrayLikeType {
[key: number]: string
}
const obj: ArrayLikeType = ["hello", "world"]
console.log(obj[0], obj[1]) // "hello" "world"
type userType = {
[key: string | **boolean**]: string; // boolean은 불가능합니다
}
type userType = {
[key: string]: string;
};
let user: userType = {
이름: "배트맨",
이름: "슈퍼맨", // 중복된 key
나이: "40"
};
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 타입인 객체만 허용
해야 합니다
총 급여 계산을 하지 못하므로 에러를 발생시키는 것입니다
클래스가 인터페이스를 구현할 때 인터페이스에 정의되지 않은 새로운 속성이 추가되어도 오류를 발생시키지 않습니다.
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가지의 방법을 사용할 수 있습니다
tsconfig.json 설정 파일의 noImlictAny 옵션 값을 false로 변경하는 것입니다.
{
"compilerOptions": {
...,
"noImplicitAny": false,
...
}
}
하지만 TypeScript 프로그래밍 시, 암묵적으로 any 타입을 사용하는 것을 피하고자 한다면 좋은 방법이 될 수 없습니다.
바로 인덱스 시그니처
를 사용하는 것입니다
이 방법은 객체의 새로운 추가 속성을
명시적으로 any 타입으로 설정한 것으로 오류를 출력하지 않습니다.
interface ButtonInterface {
onInit?():void;
onClick():void;
// 인덱스 시그니처
[prop:string]: any;
}
const button:ButtonInterface = {
type: 'button',
disabled: false,
onClick() { console.log('버튼 클릭') }
};
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가지의 방법을 사용할 수 있습니다
type assertion
const obj = {
foo: 'hello',
};
let propertyName1 = 'foo';
console.log(obj[propertyName1 as 'foo']);
바로 인덱스 시그니처
를 사용하는 것입니다
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'.
}
출처 :