2. 이펙티브 타입스크립트

작성중 ..

아이템3. 코드의 생성과 타입이 관계없음을 이해하기

런타임에는 타입 체크가 불가능합니다.

interface Square {
	width: number;
}
interface Rectangle extends Square {
	height: number;
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
	if (shape instanceof Rectangle) {
		// "Rectangle"은 형식만 참조하므로 ~ 여기서는 값으로 사용되고 있습니다.
	}
	// 생략...
}

instanceOf 체크는 런타임에 일어나지만, Rectangle 은 타입이기 때문에 런타임 시점에 아무런 역할을 할 수 없습니다. 위의 코드에서 다루고 있는 shape 타입을 명확하게 하려면 아래와 같이 처리할 수 있습니다.

function calculateeArea(shape: Shape) {
	if ('height' in shape) {
		// 타입이 Rectangle!
	}
}

// 또 다른 기법인 tag 기법
interface Square {
	kind: 'square';
	width: number;
}
interface Rectangle {
	kind: 'rectangle';
	width: number;
	height: number;
}
type Shape = Square | Rectangle; // 태그된 유니온

function calculateArea(shape: Shape) {
	if (shape.kind === 'rectangle') {
		// type narrowing success!
	}
}

// 타입을 클래스로 만들면 런타임에도 존재한다!
class Square {
	constructor(public width: number) {}
}
class Rectangle extends Square {
	constructor(public width: number, public: height: number) {
		super(width);
	}
}
type Shape = Square | Rectangle

function calculateArea(shape: Shape) {
	if (shape instanceof Rectangle) {
		shape; // 타입이 Rectangle
	}
	// 생략...
}

아이템4. 구조적 타이핑에 익숙해지기

타입스크립트의 타입 시스템은 항상 확장에 열려있다.

function calculateLength(v: Vector3D) {
	let length = 0;
	for (const axis of Object.keys(v)) {
		// @ts-expect-error
		const coord = v[axis];
		length += Math.abs(coord);
	}
	return length;
}

const vec3D = {x:3, y:4, z:1, address:'123 Broadway'};
calculateLength(vec3D); // NaN

위의 코드를 확인해보면 타입스크립트가 확장에 열려있다는 말이 무슨말인지 대략적으로 이해할 수 있다. 따라서 타입스크립트는 반복문에서 에러를 반환한다. 우리는 v가 다른 속성이 없다고 가정하고 코드를 작성했지만 실제로는 다른 속성이 있어도 타입스크립트는 에러를 반환하지 않는다.

아이템7. 타입이 값들의 집합이라고 생각하기

function getKey<K extends string>(val: any, key: K) {
	// ...
}

아이템13. 인터페이스와 타입의 차이점

interface

아이템19. 추론 가능한 타입을 사용해 장황한 코드 방지하기

타입스크립트는 타입 추론을 적극적으로 지원하기 때문에 타입 추론이 가능한 코드는 타입 명시를 최대한 지양하는 것이 좋다. 타입 구문을 생략하여 방해되는 것들을 최소화하고 코드를 읽는 사람이 구현 로직에 집중하도록 하는 것이 좋다.

타입이 추론될 수 있음에도 여전히 타입을 명시해야하는 상황 중 하나는 객체 리터럴을 정의할 때이다.

interface Product {
	name: string;
	id: string;
	price: number
}
const furby={
	name: 'Furby',
	id: 1231201,
	price: 35,
}
declare function logProduct(product: Product) => void;
logProduct(furby) // id는 string 형식이어햐 하나 타입 오류를 잡아주지 않음

또한, 반환타입을 명시하면 불필요한 에러를 방지할 수 있다. 미리 타입을 명시하는 방법은, 함수를 구현하기 전에 테스트를 먼저 작성하는 TDD와 비슷하다. 전체 타입 시그니처를 먼저 작성하면 구현에 맞추어 주먹구구식으로 시그니처가 작성되는 것을 방지하고 제대로 원하는 모양을 얻게 된다.

아이템22. 타입 좁히기

Type Narrowing은 익숙하게 사용하는 기술이다. 그러나 다음과 같은 예시에서 타입을 섣불리 판단하는 실수를 저지르기 쉬우므로 다시 한번 꼼꼼히 따져봐야 한다.

const el = document.getElementById('foo') // HTMLElement | null
if (typeof el === 'object') {
	el;  // 타입이 HTMLElement | null
}

이는 typeof null이 object이기 때문에 생긴 문제이다.

function foo(x?: number|string|null) {
	if (!x) {
		x;  // 타입이 string|number|null|undefined
	}
}

이는 빈 문자열('')과 0 모두 false가 되기 때문에 타입은 전혀 좁혀지지 않았다.

요약