Object로 구성된 Array에서 특정 property 값을 타입으로 추출하기. feat. type guard
Object array에서 타입 추출하기
typescript에서 같은 형태의 object를 담은 array에서 object의 특정 키값을 추출할 상황이 생각보다 많이 나온다. 이참에 한번 정리해보자.
The Problem
웹 사이트를 만들다가 객체 배열을 관리하다 생긴 문제였고 typescript를 사용할 때 많이 마주치는 경우인 것 같아 예시를 통해 정리해보려 한다. 예시를 위해 작성된 코드이기에 내용은 이상하더라도 구조는 같으니 어떻게 처리하는지만 봐주셨으면 한다.
사람들의 데이터를 관리하고 처리하는 상황을 예시로 들어보자. 모든 사람들에 대한 정보를 담은 people 배열이 있고 사람은 학생 혹은 선생이다. 또 사람의 이름은 유니크하다고 가정해보자. class를 사용해도 되지만 간단한 예시이니 그냥 객체 리터럴로 생성하겠다.
type TRoll = 'student' | 'teacher';
type TPeople = {
name: string;
age: number;
address: {
country: string;
city: string;
};
roll: TRoll;
};
const people: TPeople[] = [
{
name: 'cloer',
age: 3,
address: {
country: 'Korea',
city: 'Seoul',
},
roll: 'student',
},
{
name: 'Seokgyu',
age: 10,
address: {
country: 'Canada',
city: 'Toronto',
},
roll: 'teacher',
},
];
사람 이름을 받아서 그 사람이 학생인지 확인하는 함수를 만들어보자.
function isStudent(name: string) {
return !!people.find((person) => person.name === name);
}
간단하게 이런식으로 작성할 수 있을 것이다. 여기서 isStudent의 파라미터 name의 타입이 string인 것이 마음에 안 들면서 문제가 시작된다. name의 타입이 string이면 존재하지도 않는 사람을 인자로 넣을 수 있다. 즉 name의 타입이 people 배열에 존재하는 사람의 이름이었으면 좋겠다.
객체 배열에서 타입 추출
type TName = 'cloer' | 'Seokgyu';
function isStudent(name: TName) {
return !!people.find((person) => person.name === name);
}
이렇게 해결 할수는 있겠지만 이렇게 할 사람은 없을 것이다. 예시를 위해 두명만 있지만 5000명이면 5000명 이름을 모두 유니온으로 엮어야 한다. 어쨌든 원하는 최종 형태는 위와 같다.
1. const assertion
이때 type assertion과 typeof를 사용할 수 있다. 먼저 people을 const assertion으로 타입을 확정 짓고 people에 typeof를 붙이면 people객체와 중첩 객체까지 똑같이 생긴 타입을 얻을 수 있다.
const people = [
{
name: 'cloer',
age: 3,
address: {
country: 'Korea',
city: 'Seoul',
},
roll: 'student',
},
{
name: 'Seokgyu',
age: 10,
address: {
country: 'Canada',
city: 'Toronto',
},
roll: 'teacher',
},
] as const;
typeof people // people array와 완벽히 동일한 형태의 타입
2. 타입 내부 타입 접근
타입도 일반 객체와 비슷한 방법으로 다룰 수 있다. people[number]을 하면 people 배열에 number 타입의 인덱스로 접근할 수 있는 모든 객체가 유니온으로 묶인 타입이 반환된다. people[ i : number ]로 나올 수 있는 모든 경우의 수 인 셈이다.
type T = typeof people[number] // { name:'cloer', ... } | { name:'Seokgyu', ... }
3. 타입 내부의 내부 타입
위와 마찬가지로 반환된 모든 가능성에 공통으로 존재하는 name 프로퍼티에 접근하면 반환되는 모든 가능성 이 결과로 나온다.
type T = typeof people[number]['name'] // 'cloer' | 'Seokgyu'
그럼 여기서 질문 people 배열에 name 속성이 없는 객체가 있다면 어떻게 될까? 한번 추측해보자. 필자는 객체에 없는 속성에 접근했고 반환되는 모든 가능성 이기 때문에 undefined가 유니온으로 묶여 있을 거라고 추측했다. 즉 아래와 같이 추측했다.
{}.name // undefined
const people = [
{ name: 'cloer' },
{}
] as const
type T = typeof people[number]['name'] // 'cloer' | undefined
하지만 너무 javascript 같은 생각이었다. typescript에선 첫 줄부터 틀렸다. {}.name 부터 에러다.
아무튼 이렇게 원하는 결과를 얻을 수 있다. 조금 더 응용해보자.
특정 property로 type 구분하기
이번에는 type에 따라 payload가 달라지는 데이터를 다뤄보자.
const data = [
{ type: 'text', payload: { fontSize: 10, content: 'Hello world!' } },
{ type: 'image', payload: { width: 10, height: 10, src: 'https://image.cloer.com/images/image.svg' } },
] as const;
type TEachDataType = {
[K in typeof data[number]['type']]: Extract<typeof data[number], { type: K }>;
};
declare const textData: TEachDataType['text'];
textData.payload.fontSize // OK
textData.payload.src // Error
declare const imageData: TEachDataType['image'];
imageData.payload.fontSize // Error
imageData.payload.src // OK
위에서 했던 것과 마찬가지로 데이터에서 타입을 추출해서 쓸 수 있다.
혹은 사용자 정의 type guard를 사용할 수 있을 것 같다. 아래 방법이 더 직관적이고 하드 코딩된 데이터에서 타입을 뽑아 쓰는 상황이 아닌 데이터 타입을 정의하고 외부(DB 혹은 외부 api)에서 데이터를 가져와서 쓰는 상황에서 아래 방법이 더 좋아 자주 쓰게 될 것 같다.
type TTextData = {
type: 'text';
payload: { fontSize: number; content: string };
};
type TImageData = {
type: 'image';
payload: { width: number; height: number; src: string };
};
type TDataType = TTextData | TImageData;
function isTextData(aData: TDataType): aData is TTextData {
return aData.type === 'text';
}
const data: TDataType[] = [
{ type: 'text', payload: { fontSize: 10, content: 'Hello world!' } },
{ type: 'image', payload: { width: 10, height: 10, src: 'https://image.cloer.com/images/image.svg' } },
];
const aData = data[0];
isTextData(aData) ? aData.payload.fontSize : aData.payload.src;
'Languages > JS∕TS' 카테고리의 다른 글
[TS] 타입이론과 고급 타입 추론 (7) | 2024.11.14 |
---|---|
[TS] interface, impliments, extends 예시 (0) | 2022.02.23 |
[TS] 기본 자료형 (0) | 2022.02.22 |
[코어 자바스크립트] 메모리 동작과 mutability, immutability (0) | 2021.12.25 |
[JS] 호이스팅과 TDZ (0) | 2021.10.05 |