『우아한 타입스크립트 with 리액트』 읽기 ❶

minkyung·2023년 11월 28일
0
post-thumbnail

✍︎

책을 읽으면서 새로 알게 된 것, 애매모호하게 알고 있었던 것, 아직도 이해 안되는 것, 책에서 알려주는 노하우 등을 메모하는 글입니다. 매우 주관적인 흐름을 타고 있습니다.


타입스크립트 문법인 type으로 선언한 내용은 자바스크립트 런타임에서 제거되기 때문에 값 공간과 타입 공간은 서로 충돌하지 않는다.


타입과 값이 혼용되는 것 말고도 값과 타입 공간에 동시에 존재하는 심볼도 있다. 대표적인 것이 classenum이다.

타입스크립트 코드에서 클래스는 값과 타입 공간 모두에 포함될 수 있다.

클래스와 마찬가지로 타입스크립트 문법인 enum 역시 런타임에 객체로 변환되는 값이다. enum은 런타임에 실제 객체로 존재하며, 함수로 표현할 수도 있다.


우아한 형제들에서는 enum을 어떻게 사용할까?

  • 배달이 enum은 트리쉐이킹이 되지 않기 때문에 번들 사이즈에 영향을 줄 수 있지만, const enum을 사용하면 해결할 수 있음. 사실 enum을 쓴다고 해서 전체 파일의 번들 사이즈가 서비스에 영향을 미칠 정도로 커지지 않으니 크게 고민하지는 않음
    enum 외에 const enum을 사용하나요?
  • 배달이 enumaration(열거) 폴더를 따로 만들어서 사용하고 있음. 이 폴더에서 정의한 enum을 외부에서 전역적으로 참조할 때는 const enum을 사용함. const enum은 빌드 과정에서 참조 값만 남기기 때문에 트리쉐이킹이 된다는 장점도 있음
  • const enumenum과 다르게 직접적인 값으로 치환되기 때문에 전체 네임스페이스에 접근하지 못하고 순회할 수도 없다는 단점 때문에 const enum을 사용하지는 않음

⭐️ 트리쉐이킹
자바스크립트, 타입스크립트에서 사용하지 안흔 코드를 삭제하는 방식이다. ES6 이후의 최신 애플리케이션 개발 환경에서는 웹팩, 롤업과 같은 모듈 번들러를 사용한다. 이러한 도구로 번들링 작업을 수행할 때 사용하지 않는 코드는 자동으로 삭제된다. CommonJS 모듈은 트리쉐이킹을 지원하지 않지만 ES6 이후에는 파일 내 특정 모듈만 임포트하면 해당 모듈을 사용하지 않는 파일 코드는 삭제되어 더 작은 크기의 번들링 파일을 생성할 수 있게 되었다.


자바스크립트의 클래스는 결국 함수이기 때문에 값 공간에서 type of Developer의 값은 function이 된다.

class Developer {
	name:string;
	sleepingTime:number;
	
	constructor(name:string, sleepingTime:number){
		this.name = name;
		this.sleepingTime = sleepingTime;
	}
}

const d = typeof Developer;
type T = typeof Developer;

// d 의 값은 'function'
// T 는 'typeof Developer'

타입스크립트에서는 타입 단언이라 부르는 문법을 사용해서 타입을 강제할 수도 있는데 as 키워드를 사용하면 된다. 타입 단언은 개발자가 해당 값의 타입을 더 잘 파악할 수 있을 때 사용되며 강제 형 변환과 유사한 기능을 제공한다.

다른 언어의 타입 캐스팅과 타입스크립트의 타입 단언은 유사한 부분도 있지만 일치하는 개념은 아니다. 결국 타입스크립트 코드는 자바스크립트 코드로 변환되고 타입스크립트의 타입 시스템과 문법은 컴파일 단계에서 제거된다. 따라서 컴파일 단계에서는 타입 단언이 형 변환을 강제할 수 있지만 런타임에서는 효력을 발휘하지 못한다.

const loaded_text : unknown;
// 어딘가에서 unknown 타입 값을 전달받았다고 가정

const validateInputText = (text:string) => {
	if (text.length < 10) return "최소 10글자 이상 입력해야합니다.";
	return "정상 입력된 값입니다.";
}

validateInputText(loaded_text as string);
// as 키워드를 사용해서 string으로 강제하지 않으면 타입스크립트 컴파일러 단계에서 에러 발생

null은 명시적, 의도적으로 값이 아직 비어있을 수 있음을 보여준다.


bigInt

ES2020에서 새롭게 도입된 데이터 타입으로 타입스크립트 3.2버전부터 사용할 수 있다. 이전의 자바스크립트에서는 가장 큰 수인 Number.MAX_SAFE_INTEGER(2531)(2^{53}-1)를 넘어가는 값을 처리할 수 없었는데 bigInt를 사용하면 이보다 큰 수를 처리할 수 있다. number타입과 상호작용은 불가능하다.

const bigNumber1: bigInt = BigInt(99999999999);
const bigNumber2: bigInt = 99999999999;

symbol

ES2015에서 도입된 데이터 타입으로 Symbol() 함수를 사용하면 어떤 값과도 중복되지 않는 유일한 값을 생성할 수 있다. 타입스크립트에는 symbol 타입과 const 선언에서만 사용할 수 있는 unique symbol 타입이라는 symbol의 하위 타입도 있다.

const MOVIE_TITLE = Symbol("title");
const MUSIC_TITLE = Symbol("title");

console.log(MOVIE_TITLE === MUSIC_TITLE);
// false

let SYMBOL: unique symbol = Symbol();

타입스크립트의 모든 타입은 기본적으로 nullundefined를 포함하고 있다. 하지만 ts-config의 strictNullChecks 옵션을 활성화 했을 때는 사용자가 명시적으로 해당 타입에 null이나 undefined를 포함해야만 nullundefined를 사용할 수 있다. 그렇지 않으면 nullundefined가 될 수 있는 경우에 타입스크립트 에러가 발생하는데 보통 타입가드로 nullundefined가 되는 경우를 걸러낸다. !연산자를 사용해서 타입을 단언하는 방법도 있다. 이를 통해 사용자는 해당 참조가 null이나 undefined가 아니라고 보장할 수 있다. 일반적으로 타입 가드를 사용하는 것이 더 안전하다고 여겨져 단언문보다 타입 가드가 좀 더 선호되는 경향이 있다.


자바스크립트에서 빈 객체를 생성하기 위해 const obj = {}; 와 같은 구문을 사용할 수 있다. 타입스크립트 역시 이에 대응하는 타입으로 {}를 사용할 수 있는데 자바스크립트와 마찬가지로 빈 객체임을 의미한다. 따라서 {} 타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없다. 사실 빈 객체 타입을 지정하기 위해서는 {}보다 유틸리티 타입으로 Record<string, never>처럼 사용하는게 바람직하다.


객체를 타이핑하기 위해 자주 사용하는 키워드로 typeinterface가 있다.

팀 내에서 type이나 interface만을 써야하는 상황이 있었나요?

  • 배달이 객체 지향적으로 코드를 짤 때, 특히 상속하는 경우에는 interface를 사용했던 것 같음. 예를 들어 extendsimplements를 사용할 때
  • 냥이 유니온 타입이나 교차 타입 등 type 정의에서만 쓸 수 있는 기능을 활용할 때 type을 사용함. interface키워드는 예를 들어 다이얼로그 컴포넌트를 만들 때, 사이즈가 다른 다이얼로그끼리 같은 속성을 공유하는 기준 인터페이스를 정의하고 확장할 때 사용함

any 타입을 어쩔 수 없이 사용해야할 때

  1. 개발 단계에서 임시로 값을 지정해야할 때
    매우 복잡한 구성 요소로 이루어진 개발 과정에서 추후 값이 변경될 가능성이 있거나 아직 세부 항목에 대한 타입이 확정되지 않은 경우가 생길 수 있음. 이럴 때 값을 any로 지정하면 타입을 세세하게 명시하는 데 소요되는 시간을 절약할 수 있음

  2. 어떤 값을 발아올지 또는 넘겨줄지 정할 수 없을 때

  3. 값을 예측할 수 없을 때 암묵적으로 사용
    외부 라이브러리나 웹 API의 요청에 따라 다양한 값을 반환하는 API가 존재할 수 있음. 예를 들어, Fetch API의 일부 메서드는 요청 이후의 응답을 특정 포맷으로 파싱하는데 이 때 반환 타입이 any로 매핑되어 있는 것을 볼 수 있음


unknown

unknown 타입은 any 타입과 유사하게 모든 타입의 값이 할당될 수 있다. 그러나 any 를 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없다.

anyunknown
어떤 타입이든 any 타입에 할당 가능어떤 타입이든 unknown 타입에 할당 가능
any 타입은 어떤 타입으로도 할당 가능 (단 never는 제외)unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능

unknown 타입은 타입스크립트 3.0이 릴리스 될 때 추가되었는데 기존 타입 시스템에서 부족한 부분을 보완하기 위해 등장했다.

unknown 타입은 어떤 타입이 할당되었는지 알 수 없음을 나타내기 때문에 unknown 타입으로 할당된 변수는 값을 가져오거나 내부 속성에 접근할 수 없다. 이는 unknown 타입으로 할당된 변수는 어떤 값이든 올 수 있음을 의미하는 동시에 개발자에게 엄격한 타입 검사를 강제하는 의도를 담고 있다.

앞서 어떤 값이 할당될지 파악하기 어려운 상황에서 any 타입을 지정하여 임시로 문제를 회피하는 예시도 살펴보았다. 그리고 나중에 any 타입을 특정 타입으로 수정해야하는 것을 깜박하고 누락하면 어떤 값이든 전달될 수 있기 때문에 런타임에 예상치 못한 버그가 발생할 가능성이 높아진다는 것도 설명했다. unknown 타입은 이러한 상황을 보완하기 위해 등장한 타입이다.

unknown은 어떨 때 사용할 수 있을까요?

  • 냥이 any보다는 좀 더 많이 사용하는 것 같음. any는 무엇이든 괜찮다. unknown은 뭔지 모르지만 하나씩 테스트하면서 뭔지 알아내보자라는 의미라고 생각하는데 후자의 논리 전개가 필요할 때 unknown을 사용함.
  • 예상할 수 없는 데이터라면 unknown을 사용함. 타입스크립트 4.4부터 try-catch 에러의 타입이 any에서 unknown으로 변경되어서 에러 핸들링할 때도 unknown을 사용함

never

값을 반환할 수 없는 타입을 말함

  1. 에러를 던지는 경우
    function generateError(res:Response):never{
    	throw new Error(res.getMessage());	
    }
  2. 무한히 함수가 실행되는 경우
    드물지만 함수 내에서 무한 루프를 실행하는 경우가 있을 수 있다.

useState는 튜플 (!! 그러고보니)


열거형에 사용할 때는 주의해야할 점이 있다. 먼저 숫자로만 이루어져 있거나 타입스크립트가 자동으로 추론한 열거형은 안전하지 않은 결과를 낳을 수 있다. 맨 처음 예시를 보면 역방향으로도 접근할 수 있음을 보여준다. 여기서 할당된 값을 넘어서는 범위로 역방향으로 접근하더라도 타입스크립트는 막지 않는다. 이러한 동작을 막기 위해 const enum으로 열거형을 선언하는 방법이 있다. 이 방식은 역방향으로의 접근을 허용하지 않기 때문에 자바스크립트에서의 객체에 접근하는 것과 유사한 동작을 보장한다.

그러나 const enum으로 열거형을 선언하더라도 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못한다. 문자열 상수 방식으로 열거형을 사용하는 것이 숫자 상수 방식보다 더 안전하며 의도하지 않은 값의 할당이나 접근을 방지하는 데 도움이 된다.

열거형은 타입스크립트 코드가 자바스크립트로 변환될 때 즉시 실행함수(IIFE) 형식으로 변환되는 것을 볼수 있다.


교차타입

type ProductItem = {
	id:number;
	name:string;
	type:string;
}

type ProductItemWithDiscount = ProductItem & {discountAmount: number};

인덱스 시그니처

특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용하는 문법이다. 인터페이스 내부에 [Key: K]: T 꼴로 타입을 명시해주면 되는데 이는 해당 타입의 속성 키는 모두 K 타입이어야하고 속성값은 모두 T 타입을 가져야한다는 의미다.

profile
프론트엔드 개발자

0개의 댓글