
4장은 실제 코드를 작성할 때 유용한 것들이 많이 포함되어 있었다.
유효한 상태와 무효한 상태를 둘 다 표현하는 타입은 혼란을 초래하기 쉽기 때문에 유효한 상태만 표현하는 타입을 지향해야 한다.
매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 때는 타입의 범위가 더 구체적이어야 한다.
ageNum 보다는 age로 하고, 그 타입이 number임을 명시하는 게 좋다.timeMs는 time보다 훨씬 명확하고, temperatureC는 temperature보다 훨씬 명확하다.)strictNullChecks 설정을 하지 않는다면 설계적 결함을 발견하지 못할 수도 있다.
예를 들면, 아래의 코드는 최댓값이나 최솟값이 0인 경우, 값이 덧씌워져 버린다. 또한 nums 배열이 비어져있다면 함수는 [undefined,undefined]를 반환한다.undefined 를 포함하는 객체는 다루기 어렵고 절대 권장하지 않는다.
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)
}
}
return [min, max]
}
위의 코드의 해결법은 (!) null 아님 단언을 사용하던가 if 구문으로 체크할 수 있다.
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
}
// 1. (!) null 아님 단언 사용
const [min,max] = extent([0,1,2])!
// 2. if 구문으로 체크
const range = extent([0,1,2])!
if (range) {
const [min, max] = range;
const span = max - min;
}
➡️ strictNullChecks 설정을 하면 코드가 좀 더 복잡해지고, 어떻게 하나 하나 null 체크를 하지 싶었는데 이 챕터를 보고나니 어떤식으로 체크를 해야하는지 도움이 되었고, 프로젝트에도 적용해 볼 생각이다.
// Bad
interface Layer {
layout: FillLayout | LineLayout | PointLayout;
paint: FillPaint | LinePaint | PointPaint;
}
// Good
interface FillLayer {
layout: FillLayout;
paint: FillPaint;
}
interface LineLayer {
layout: LineLayout;
paint: LinePaint;
}
interface PointLayer {
layout: PointLayout;
paint: PointPaint;
}
type = FillLayer | LineLayer | PointLayer;// Bad
interface Person {
name: string;
// 다음은 둘 다 동시에 있거나 동시에 없다
placeBirth?: string;
dateBirth?: Date;
}
// Good
interface Person {
name: string;
birth?: {
place: string;
date: Date;
}
}string은 any와 비슷한 문제를 갖고 있기 때문에 모든 문자열을 할당할 수 있는 string 타입보다는 문자열 리터럴 타입의 유니온을 사용하면 좋다.
// Bad
interface Album {
title: string;
releaseDate: string;
recordingType: string;
}
// Good
type RecordingType = 'studio' | 'live';
interface Album {
title: string;
releaseDate: Date;
recordingType: RecordingType;
}
타입을 구체적으로 작성할 수록 버그를 많이 잡고 타입스크립트가 제공하는 도구를 활용할 수 있게 됩니다.
하지만 타입 선언의 정밀도를 높이는 일은 주의를 기울여야 한다. 실수가 발생하기 쉽고 잘못된 타입은 차라리 타입이 없는거 보다 못할 수 있기 때문이다.
또한 타입 선언을 더 구체적이라고 하여도, 자동 완성을 방해한다면 타입스크립트 경험을 해치기 때문에 주의를 기울여야 한다.
데이터에 드러나지 않는 예외적인 경우들이 문제가 될 수 있으므로 데이터보다는 명세로부터 코드를 생성하는 것이 좋다.
가독성을 높이고, 추상화 수준을 올리기 위해서 해당 분야의 요어를 사용해야 한다.
interface animal {
name: string;
}
interface animal {
commonName: string;
}
name은 매우 일반적인 용어이다. 동물의 학명인지 일반적인 명칭인지 알 수가 없다. commonName으로 더 구체적인 용어로 대체했다.
타입스크립트의 구조적 타이핑 때문에 가끔 코드가 이상한 결과를 낼 수 있다.
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}); // 정상
const vec3D ={x: 3, y: 4, z: 5}
calculateNorm(vec3D); // 정상!
calculateNorm 함수가 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({x: 3, y: 4})); // 정상
const vec3D ={x: 3, y: 4, z: 5}
calculateNorm(vec3D); // Error..
만약 vec3D에 _brand: '2d'라고 추가하는 것과 같은 악의적인 사용을 막을 수는 없다. 하지만 단순한 실수를 방지하기에는 좋다.