정확하게 타입을 모델링할 수 없다면, 부정확하게 모델링하지 말아야 한다. 또한 any와 unknown을 구별해서 사용해야 한다.
$npm i --save-dev @types/geojson
import { Feature } from "geojson";
function calculateBox(f: Feature): BoundingBox | null {
let box: BoundingBox | null = null;
const helper = (coords: any[]) => {
// ...
};
const { geometry } = f;
if (geometry) helper(geometry.coordinates); // ❌ 'Geometry'형식에 'coordinates'속성이 없습니다.
}
geometry에 coordinates 속성이 있다고 가정한 것이 문제, GeoJSON은 GeometryCollection(coordinates 속성이 없음)일 수 있다.
✔️ 해결 방법
const geometryHeloper = (g: Geometry) => {
if (geometry.type === "GeometryCollection") {
geometry.geometries.forEach(geometryHelper);
} else {
helper(geometry.coordinates); // 정상
}
};
const { geometry } = f;
if (geometry) geometryHelper(geometry);
명세를 기반으로 타입을 작성한다면 현재까지 경험한 데이터뿐만 아니라 데이터에 드러나지 않는 예외적인 경우와 사용 가능한 모든 값에 대해서 작동한다는 확신을 가질 수 있다.
동물들의 데이터베이스 구축
interface Animal {
name: string;
endangered: boolean;
habitat: string;
}
const leopard: Animal = {
name: "Snow Leopard",
endangered: false,
habitat: "tundra",
};
👎 문제점
✔️ 개선 후 코드
interface Animal {
commonName: string;
genus: string;
species: string;
status: ConservationStatus;
climates: KoppenClimate[];
}
type ConservationStatus = "EX" | "EW" | "CR" | "EN" | "VU";
type KoppenClimate = "Af" | "Am" | "As" | "Aw" | "BSh" | "BSk" | "BWh" | "BWk";
const snowLeopard: Animal = {
commonName: "Snow Leopard",
genus: "Panthera",
species: "Uncia",
status: "EW",
climates: ["Af", "BSk"],
};
👍 개선점
엄선된 타입, 속성, 변수의 이름은 의도를 명확히 하고 코드와 타입의 추상화 수준을 높여 준다.
✔️ 타입, 속성, 변수에 이름 붙힐 때 지켜야할 규칙
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 }); // 정상, 5
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // 정상, 5
위 코드는 구조적 타이핑 관점에서는 문제가 없지만 수학적으로 따지면 2차원 벡터를 사용해야 이치에 맞는다. 3차원 벡터를 허용하지 않게 하려면 공식 명칭을 사용하면 된다. 공식 명칭 개념을 타입스크립트에서 흉내 내려면 '상표(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);
}
calculateNorm(vec2D(3, 4)); // 정상, 5
const vec3D = { x: 3, y: 4, z: 1 };
calculateNorm(vec3D); // ❌ '_brand'속성이 ... 형식에 없습니다
상표(_brand)를 사용해서 calculateNorm 함수가 vector2D 타입만 받는 것을 보장한다.