이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅유니온을 가진 인터페이스는 프로퍼티간의 관계가 불분명하기 때문에 에러를 유발할 수 있다
✅유니온의 인터페이스보다 인터페이스의 유니온이 더 정확하고 타입스크립트가 이해하기도 좋다
✅타입에 태그를 넣는 tagged union을 사용하는 편이 좋다
📍인터페이스들의 유니온 예시 1
인터페이스의 프로퍼티를 유니온 타입으로 사용하는 것보다, 인터페이스들의 유니온 타입 형태로 사용하는 것이 바람직하다
- 예시
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
// bad case
// layout과 paint가 일치하지 않는 실수를 범할 수 있다
/*
interface Layer {
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint;
}
*/
// good case
// layout과 paint를 통일하여 사용할 수 있다
interface FillLayer {
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
layout: PointLayout;
paint: PointPaint;
}
type Layer = FillLayer | LineLayer | PointLayer;
이때, tagged union을 사용하여 type narrowing을 활용하는 형태로 업그레이할 수 있다
- 예시
type FillPaint = unknown;
type LinePaint = unknown;
type PointPaint = unknown;
type FillLayout = unknown;
type LineLayout = unknown;
type PointLayout = unknown;
// 문자열 리터럴 타입을 인터페이스에 추가하여 tagged union 준비
interface FillLayer {
type: 'fill';
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
type: 'line';
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
type: 'paint';
layout: PointLayout;
paint: PointPaint;
}
// tagged union
type Layer = FillLayer | LineLayer | PointLayer;
// type narrowing
function drawLayer(layer: Layer) {
if (layer.type === 'fill') {
const { paint } = layer; // Type is FillPaint
const { layout } = layer; // Type is FillLayout
} else if (layer.type === 'line') {
const { paint } = layer; // Type is LinePaint
const { layout } = layer; // Type is LineLayout
} else {
const { paint } = layer; // Type is PointPaint
const { layout } = layer; // Type is PointLayout
}
}
⭐어떤 타입을 tagged union으로 표현할 수 있다면, 그렇게 하는 것이 좋다
📍옵셔널 프로퍼티를 tagged union으로 다루기
- 옵셔널 프로퍼티를 다루는 좋은 방법
- 예시
// 개선 전
interface Person1 {
name: string;
// 아래 코드는 둘 다 동시에 있거나 없다
placeOfBirth?: string;
dateOfBirth?: Date;
}
// 개선 후
// 2개의 프로퍼티를 하나로 모아서 2개 중 1개만 존재하는 경우를 걸러낼 수 있다
interface Person2 {
name: string;
birth?: {
place: string;
date: Date;
}
}
// 걸러내는 예시1
const alanT: Person2 = {
name: 'Alan Turing',
birth: {
// ~~~~ Property 'date' is missing in type
// '{ place: string; }' but required in type
// '{ place: string; date: Date; }'
place: 'London'
}
}
// 함수
function eulogize(p: Person2) {
console.log(p.name);
const {birth} = p;
if (birth) { // 1번의 런타임 타입 체크만 하면 됨
console.log(`was born on ${birth.date} in ${birth.place}.`);
}
}
tagged union을 활용하면 더 개선할 수 있다
- name 과 birth 프로퍼티를 분리하여 tagged union으로 구성한다
- 예시
interface Name {
name: string;
}
interface PersonWithBirth extends Name {
placeOfBirth: string;
dateOfBirth: Date;
}
type Person = Name | PersonWithBirth;
function eulogize(p: Person) {
if ('placeOfBirth' in p) {
p // Type is PersonWithBirth
const {dateOfBirth} = p // OK, type is Date
}
}