이펙티브 타입스크립트 (댄 밴더캄 지음) 를 읽고 정리
📍요약
✅구조적 타이핑 때문에 값의 타입을 세밀하게 구분하지 못할 때가 있다
이 때 상표를 붙이는 것을 고려할 수 있다
✅상표 기법은 타입 시스템에서만 동작하지만, 런타임에서 상표를 검사하는 것과 동일한 효과를 얻을 수 있다
📍상표(brand) 붙이기
아래 코드에서는 구조적 타이핑으로 calculateNorm의 매개변수 자리에 Vector2D가 아닌 3D 타입도 에러없이
가능하게 된다
interface Vector2D {
x: number;
y: number;
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
calculateNorm({x: 3, y: 4}); // OK, result is 5
const vec3D = {x: 3, y: 4, z: 1};
calculateNorm(vec3D); // OK! result is also 5
calculateNorm 함수가 Vector2D만 갖게 하려면 상표를 붙이면 된다
_brand 프로퍼티를 넣어 다른 비슷한 타입이 매개변수로 들어오는 것을 걸러낼 수 있다
interface Vector2D {
_brand: '2d';
x: number;
y: number;
}
function vec2D(x: number, y: number): Vector2D {
return {x, y, _brand: '2d'};
}
function calculateNorm(p: Vector2D) {
return Math.sqrt(p.x * p.x + p.y * p.y); // Same as before
}
calculateNorm(vec2D(3, 4)); // OK, returns 5
const vec3D = {x: 3, y: 4, z: 1}; // _brand: '2d' 프로퍼티를 as const로 넣으면 가능하긴 함..
calculateNorm(vec3D);
// ~~~~~ Property '_brand' is missing in type...
⭐그런데, 이것도 _brand: '2d' 프로퍼티를 as const로 넣으면 회피할 수 있다..
📍절대경로와 상대경로 체크하는 예제
- 타입가드 (f 함수) 를 활용하여 분기
- 타입가드 대신 타입 단언문을 사용할 수도 있지만, (path as AbsolutePath)
타입 단언문은 지양하는 것이 좋다
⭐타입 단언문을 쓰지 않고 AbsolutePath 타입을 얻는 방법
- AbsolutePath 타입을 매개변수로 받음
- 타입 가드를 통해 맞는지 체크
type AbsolutePath = string & {_brand: 'abs'}; // & : intersection type
function listAbsolutePath(path: AbsolutePath) {
// ...
}
function isAbsolutePath(path: string): path is AbsolutePath { // is : 반환값의 타입을 정의
return path.startsWith('/');
}
function f(path: string) {
if (isAbsolutePath(path)) {
listAbsolutePath(path); // path가 AbsolutePath 타입이 확실한 경우
}
// 아래는 path가 string 일 수 있을 경우
listAbsolutePath(path);
// ~~~~ Argument of type 'string' is not assignable
// to parameter of type 'AbsolutePath'
}
📍매개변수를 제한하기 위해 상표를 사용하는 예제
- 이진 탐색 코드
매개 변수는 정렬된 배열이어야 함
function binarySearch<T>(xs: T[], x: T): boolean {
let low = 0, high = xs.length - 1;
while (high >= low) {
const mid = low + Math.floor((high - low) / 2);
const v = xs[mid];
if (v === x) return true;
[low, high] = x > v ? [mid + 1, high] : [low, mid - 1];
}
return false;
}
매개변수가 정렬된 배열임을 증명하기 위해 상표 기법을 활용
- SortedList
type SortedList<T> = T[] & {_brand: 'sorted'};
function isSorted<T>(xs: T[]): xs is SortedList<T> {
for (let i = 1; i < xs.length; i++) {
if (xs[i] > xs[i - 1]) {
return false;
}
}
return true;
}
function binarySearch<T>(xs: SortedList<T>, x: T): boolean {
// COMPRESS
return true;
// END
}
📍number 타입에 상표 붙이기
- number 타입에 붙일 수 있지만 단언문을 써야 한다
산술 연산 후에 타입이 다시 number로 바뀌지만, 여러 단위가 혼재하는 코드를 문서화 할 때 괜찮을 수 있음
type Meters = number & {_brand: 'meters'};
type Seconds = number & {_brand: 'seconds'};
const meters = (m: number) => m as Meters;
const seconds = (s: number) => s as Seconds;
const oneKm = meters(1000); // Type is Meters
const oneMin = seconds(60); // Type is Seconds
// 산술 연산 이후에 타입이 number로 변함
const tenKm = oneKm * 10; // Type is number
const v = oneKm / oneMin; // Type is number