타입스크립트의 타입 시스템 [2]

DOHEE·2022년 11월 7일
0

EffectiveTypescript

목록 보기
3/11

[5] 객체 래퍼 타입 피하기

자바스크립트에는 기본형 값들에 대한 일곱가지 타입이 있다.

string, number, boolean, null, undefined, symbol, bigint

기본형들은 불변이며 메서드를 가지지 않는다.

그렇다면 "string.charAt(3)" 이런 표현은 어떻게 된 일일까? 분명 string은 기본형이라 메서드가 없어야 하는데 .charAt()은 메서드이다.

이건 javascript가 자유자재로 string과 String 객체 타입을 넘나들기 때문에 발생한다. 특정 문자열에 메서드를 사용하게 되면 javascript는 순간적으로 String 객체를 만들어 메서드를 처리한 뒤, 해당 String 객체를 제거한다.

모든 기본형들은 각 객체 타입을 갖고 있는데, 특히 string은 처음엔 잘 동작하는 것처럼 보여 더욱 유의해야 한다.

[6] 잉여 속성 체크의 한계 인지하기

우리가 const에 타입을 따로 지정하지 않고 값을 넣으면, typescript는 기본적으로 literal로 유추한다.

왜냐하면 const는 값을 변하지 않을 것임을 암시하기 때문에, 아래와 같은 코드가 있을 때 cake의 타입을 string이 아닌 cake라는 값만 있는 string literal 타입으로 유추한다.

const cake = "cake"

따라서, const 변수에 타입을 지정하고 객체 값을 넣을 때, 잉여 속성이 있는지 확인한다. 만약 원래 타입과 다른 요소가 있다면 타입체커는 오류를 보낸다.

하지만 명확한 한계가 있다.

예를 들어, title과 content가 있는 Article interface가 있다고 해보자.

interface Article {
	title: string;
    content: string;
}

앞서 말했던 것처럼 const에 타입을 Article로 지정하고 값을 넣었는데 잉여값이 있다면 에러를 보낸다. 반면에 다른 변수에 객체를 선언한 뒤 대입할 경우에는 에러가 발생하지 않는다.

const newArticle: Article = {
	title: "안녕하세요",
    content: "만나서 반갑습니다",
    author: "DOHEE"
};
// error

const article = {
	title: "안녕하세요",
    content: "만나서 반갑습니다",
    author: "DOHEE"
};
const newArticle: Article = article;
// ok

잉여 속성 체크를 이용하면 기본적으로 타입 시스템의 구조적 본질을 해치지 않으면서도 객체 리터럴에 알 수 없는 속성을 허용하지 않아 보다 안정적인 코드 작성이 가능하다.

하지만 앞선 예제처럼 중간에 다른 변수에 넣거나 타입 단언을 사용하는 경우, 잉여 속성 체크가 작동하지 않는다.

interface Cake {
	name: string;
    [otherInfo: string]: unknown;
};
// index signiture

만약 잉여 속성 체크를 원하지 않는다면, 인덱스 시그니처를 사용하여 추가적인 속성을 예상하도록 만들 수 있다.

[7] 함수 표현식에 타입 적용하기

자바스크립트에서는 함수 문장(statement)함수 표현식(expression)을 다르게 인식한다.

function DOHEE(food: string): string { 
	console.log(`DOHEE eats ${food}!`); 
};
// 문장

const DOHEE = function (food: string): string {
	console.log(`DOHEE eats ${food}!`);
};
// 표현식

const DOHEE = (food: string): string => {
	console.log(`DOHEE eats ${food}!`);
};
// 표현식

타입스크립트에서는 함수 표현식을 사용하는 것이 좋다. 함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있기 때문이다.

type EatFood = (food: string) => food;
const DOHEE: EatFood = (food) => console.log(`DOHEE eats ${food}!`);

또는 기존 함수를 타입화하여 사용할 수도 있다.

const catObesity = (name: string, height: number, weight: number): string => {
	/* 고양이의 비만 정도를 계산하는 코드*/
    if(비만) return `${name} 은(는) 비만입니다.`
    return `${name} 은(는) 비만이 아닙니다.`
};

const dogObesity: typeof catObesity = (name, height, weight) => {/*code*/};

dogObesity 위에 마우스를 올려보면 name: string, height: number, weight: number으로 매개변수의 타입과 반환값의 타입이 잘 지정되어 있는 것을 확인할 수 있다.

[8] 타입과 인터페이스의 차이점 알기

타입스크립트에서 명명된 타입(named type)을 정의하는 방법은 type과 interface, 두 가지이다.

대부분의 경우 interface와 type 둘 사이에 큰 차이가 없어 혼용해도 크게 문제가 없다.

하지만, interface와 type은 분명히 다르기 때문에 작성 당시에 어떤 것을 사용하는 것이 좋을지 생각해보는 것이 좋다.

둘의 차이점에 대해서 말하기에 앞서 공통점에 대해 간단하게 이야기해보고자 한다.

1) 지정된 요소가 없거나(optional의 경우 제외) 지정하지 않은 요소가 있을 경우 에러 보냄
2) 인덱스 시그니처 사용 가능

type dictionary = {[key: string]: string};
interface Dictionary = {
	[key: string]: string
}

3) 함수 타입을 정의할 수 있다.
4) generic을 사용할 수 있다.
5) 서로 확장(extends)이 가능하다.
6) 클래스 구현(implements)이 가능하다.

이제는 type과 interface의 차이점이다.

1) 유니온 타입은 있지만 유니온 인터페이스는 없다.
2) type 키워드는 일반적으로 interface보다 쓰임새가 많다.
3) type은 복잡한 타입 설정이 가능하다.
4) type은 배열이나 튜플을 표현하기 쉽다.
=> interface로 튜플 구현 시 사용할 수 없는 키워드가 생길 수 있다. (ex. concat)

type tuple = [number, number];
interface tuple {
	0: number;
    1: number;
    length: 2;
};

5) interface는 선언 병합(declaration merging)이 가능하다.

interface Dessert {
	name: string;
    price: number;
};

interface Dessert {
	flavor: string[];
};

const pancake: Dessert = {
	name: "pancake",
    price: "$10",
    flavor: ["sweet", "soft", "fruity"]
};
// ok

간단히 정리하자면 복잡한 타입의 경우 type을 사용해야 한다. 두 가지 방법 모두로 표현 가능하다면 일관성과 보강의 관점에서 고려해봐야 한다. 이후 보강해야 할 경우 병합이 가능한 interface를 사용하는 것이 좋고, 일관되게 타입을 사용할 경우 type을 사용하는 것이 좋다.

profile
안녕하세요 : ) 천천히라도 꾸준히 성장하고 싶은 개발자 DOHEE 입니다! ( 프로필 이미지 출처 : https://unsplash.com/photos/_FoHMYYlatI )

0개의 댓글