객체 내에 속성을 동적으로 정의하거나, 엑세스 할때 사용하는 타입이라고 생각하면 된다.
4가지 정도의 유형이 있다.
타입이 정적으로 정의되지 않을 때 사용
동적으로 프로퍼티 할당 가능
interface StringMap {
[key: string]: string; // key is a string, value is also a string
}
const myMap: StringMap = {
name: "Alice",
city: "Wonderland",
};
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
interface Person {
name: string;
age: number;
}
type NameType = Person["name"]; // string
type AgeType = Person["age"]; // number
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
type ReadOnlyPerson = ReadOnly<Person>;
// equivalent to:
// {
// readonly name: string;
// readonly age: number;
// }
예시상황을 보자
아래와 같은 타입에서 => 각각의 프로퍼티의 타입을 정확하게 뽑아내고 싶을때는 어떻게 해야할까?
type Test = { a?: string; b?: number; c?: Array<{ aa?: number; bb?: string }>;}
처음에 시도한 방법은 => key값을 keyof연산자를 통해 key타입을 넣어주면 알아서 추론 할 수 있을 것 같았다.
하지만, key타입 자체가 리터럴타입이라 => 추론되는 결과 타입도 각 key에 맞는 value type이 아니라, 객체 전체의 유니온타입으로 추론되는 것을 알 수 있었다.
const getTest1 = (params: Test, key: keyof Test) => { return params[key];}
const value: Test = {} as const;
const result = getTest1(value, 'c');// string | number | { aa?: number; bb?: string; }[] | undefined
하지만, 제너릭과 제약조건을 함께 이용하면 이 문제를 해결 할 수 있다 .
여기서 제너릭으로 Key를 받고, 이를 keyof 연산자로 key리터럴타입으로 제약조건을 걸어준다.
TS는 그럼 들어오는 파라미터는, key값 type중 무조건 하나라고 인식하고, 해당 key에 맞는 value를 추론 할 수 있게 되는 것이다.
const getTest2 = <Key extends keyof Test>(params: Test, key: Key) => { return params[key];}
const value: Test = {} as const;
const result2 = getTest2(value, 'c');// { aa?: number; bb?: string;}[] | undefined