2024.10.27
function renderPage(state: State) {
if (state.error) {
return 'Error! Unable to load ${currentPage}: ${state.error}';
} else if (state.isLoading) {
return 'Loading ${currentPage}.••';
}
return '<h1>${currentPage}</h1>\n${state.pageText}';
}
function renderPage(state: State) {
const { currentPage } = state;
const requeststate = state.requests[currentPage];
switch (requeststate.state) {
case 'pending':
return 'Loading ${currentPage}.//';
case 'error':
return 'Error! Unable to load ${currentPage}: ${requeststate.error}';
case 'ok':
return '<h1>${currentPage}</h1>\n${requeststate.pageText}';
}
}
timeMs
는 time
보다 훨씬 명확합니다. )null
여부가 다른 값의 null
여부에 암시적으로 관련되도록 설계하면 안 됩니다.null
이거나 null
이 아니게 만들어야 합니다. 사람과 타입 체커 모두에게 명료한 코드가 될 것입니다.// 결함 버전
function extent(nums: number[]) {
let min, max;
for (const num of nums) {
if (!min) {
min = num;
max = num;
} else {
min = Math.min(min, num);
max = Math.max(max, num);
// ~~~ 'number | undefined' 형식의 인수는
// 'number' 형식의 매개변수에 할당될 수 없습니다.
}
}
return [min, max];
}
// 수정 버전
function extent(nums: number[]) {
let result: [number, number] | null = null;
for (const num of nums) {
if (!result) {
result = [num, num];
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])];
}
}
return result;
}
null
이 존재하지 않도록 하는 것이 좋습니다.strictNullChecks
를 설정하면 코드에 많은 오류가 표시되겠지만, null
값과 관련된 문제점을 찾아낼 수 있기 때문에 반드시 필요합니다.// 유니온 타입의 속성을 여러개 가지는 인터페이스
interface Layer {
type: 'fill' | 'line' | 'point1';
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint;
}
// 타입에 태그를 넣어 태그된 유니온 타입으로 인터페이스를 활용하도록 변경
interface FillLayer {
type: 'fill';
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
type: 'line';
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
type: 'paint';
layout: PointLayout;
paint: PointPaint;
}
type Layer = FillLayer | LineLayer | PointLayer;
function drawLayer(layer: Layer) {
if (layer.type === 'fill') {
const { paint } = layer; // 타입이 FillPaint
const { layout } = layer; // 타입이 FillLayout
} else if (layer.type === 'line') {
const { paint } = layer; // 타입이 LinePaint
const { layout } = layer; // 타입이 LineLayout
} else {
const { paint } = layer; // 타입이 PointPaint
const { layout } = layer; // 타입이 PointLayout
}
}
string
타입보다는 더 구체적인 타입을 사용하는 것이 좋습니다.string
타입보다는 문자열 리터럴 타입의 유니온을 사용하면 됩니다. 타입 체크를더 엄격히 할수 있고 생산성을 향상시킬 수 있습니다.// string 타입을 남발하여 사용한 코드
interface Album {
artist: string;
title: string;
releaseDate: string; // YYYY-MM-DD
recordingType: string; // 예를 들어, "live" 또는 "studio"
}
// 문자열 리터럴 타입의 유니온 사용한 코드
type RecordingType = 'studio' | 'live';
interface Album {
artist: string;
title: string;
releaseDate: Date;
recordingType: RecordingType;
}
string
보다 keyof T
를 사용하는 것이 좋습니다.any
와 unknown
를 구별해서 사용해야 합니다.// 잘못 선택한 타입의 코드
interface Animal {
name: string; // 동물의 학명인지 일반적인 명칭인지 알 수 없음
endangered: boolean; // 멸종 위기를 표현하기 위해 boolean 타입을 사용한 것이 이상함. 이미 멸종된 동물을 true로 해야 하는지 판단할 수 없음
habitat: string; // 서식지라는 뜻 자체도 불분명하기 때문에 다른 속성들보다도 훨씬 모호함
}
const leopard: Animal = {
name: 'Snow Leopard',
endangered: false,
habitat: 'tundra',
};
// 개선된 타입의 코드
interface Animal {
commonName: string;
genus: string;
species: string;
status: Conservationstatus;
climates: KoppenClimate[];
}
/**
- name은 commonName, genus, species 등 더 구체적인 용어로 대체함
- status는 동물 보호 등급에 대한 IUCN의 표준 분류 체계인 Conservation Status 사용함
- 기후를 뜻하는 climates를 사용하고, 쾨펜 기후 분류(Kdppen climate classification)를 사용함
*/
const snowLeopard: Animal = {
commonName: 'Snow Leopard',
genus: 'Panthera',
species: 'Uncia',
status: 'VU', // 취약종(vulnerable)
climates: ['ET', 'EF', 'Dfd'], // 고산대(alpine) 또는 아고산대(subalpine)
};
// 구조적 타이핑 관점에서 문제는 없지만, 논리적으로 3차원 벡터를 사용할 수 있는 상태
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
// 상표(brand)를 붙여 공식 명칭(nominal typing) 개념을 흉내낸 상태
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’ 속성이 ... 형식에 없습니다.
// 런타임에는 절대 경로(‘/')로 시작하는지 체크하기 쉽지만,
// 타입 시스템에서는 절대 경로를 판단하기 어렵기 때문에 상표 기법을 사용한 예제
type AbsolutePath = string & { _brand: 'abs' };
function ListAbsolutePath(path: AbsolutePath) {
// ...
}
function isAbsolutePath(path: string): path is AbsolutePath {
return path.startsWith('/');
}
// 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); // 타입이 Meters
const oneMin = seconds(60); // 타입이 Seconds