✍︎
책을 읽으면서 새로 알게 된 것, 애매모호하게 알고 있었던 것, 아직도 이해 안되는 것, 책에서 알려주는 노하우 등을 메모하는 글입니다. 매우 주관적인 흐름을 타고 있습니다.
타입스크립트 문법인 type
으로 선언한 내용은 자바스크립트 런타임에서 제거되기 때문에 값 공간과 타입 공간은 서로 충돌하지 않는다.
타입과 값이 혼용되는 것 말고도 값과 타입 공간에 동시에 존재하는 심볼도 있다. 대표적인 것이 class
와 enum
이다.
타입스크립트 코드에서 클래스는 값과 타입 공간 모두에 포함될 수 있다.
클래스와 마찬가지로 타입스크립트 문법인 enum
역시 런타임에 객체로 변환되는 값이다. enum
은 런타임에 실제 객체로 존재하며, 함수로 표현할 수도 있다.
우아한 형제들에서는
enum
을 어떻게 사용할까?
- 배달이
enum
은 트리쉐이킹이 되지 않기 때문에 번들 사이즈에 영향을 줄 수 있지만,const enum
을 사용하면 해결할 수 있음. 사실enum
을 쓴다고 해서 전체 파일의 번들 사이즈가 서비스에 영향을 미칠 정도로 커지지 않으니 크게 고민하지는 않음
enum
외에const enum
을 사용하나요?- 배달이 enumaration(열거) 폴더를 따로 만들어서 사용하고 있음. 이 폴더에서 정의한
enum
을 외부에서 전역적으로 참조할 때는const enum
을 사용함.const enum
은 빌드 과정에서 참조 값만 남기기 때문에 트리쉐이킹이 된다는 장점도 있음- 왕
const enum
은enum
과 다르게 직접적인 값으로 치환되기 때문에 전체 네임스페이스에 접근하지 못하고 순회할 수도 없다는 단점 때문에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
은 명시적, 의도적으로 값이 아직 비어있을 수 있음을 보여준다.
ES2020에서 새롭게 도입된 데이터 타입으로 타입스크립트 3.2버전부터 사용할 수 있다. 이전의 자바스크립트에서는 가장 큰 수인 Number.MAX_SAFE_INTEGER
를 넘어가는 값을 처리할 수 없었는데 bigInt
를 사용하면 이보다 큰 수를 처리할 수 있다. number
타입과 상호작용은 불가능하다.
const bigNumber1: bigInt = BigInt(99999999999);
const bigNumber2: bigInt = 99999999999;
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();
타입스크립트의 모든 타입은 기본적으로 null
과 undefined
를 포함하고 있다. 하지만 ts-config의 strictNullChecks
옵션을 활성화 했을 때는 사용자가 명시적으로 해당 타입에 null이나 undefined를 포함해야만 null
과 undefined
를 사용할 수 있다. 그렇지 않으면 null
과 undefined
가 될 수 있는 경우에 타입스크립트 에러가 발생하는데 보통 타입가드로 null
과 undefined
가 되는 경우를 걸러낸다. !연산자를 사용해서 타입을 단언하는 방법도 있다. 이를 통해 사용자는 해당 참조가 null
이나 undefined
가 아니라고 보장할 수 있다. 일반적으로 타입 가드를 사용하는 것이 더 안전하다고 여겨져 단언문보다 타입 가드가 좀 더 선호되는 경향이 있다.
자바스크립트에서 빈 객체를 생성하기 위해 const obj = {};
와 같은 구문을 사용할 수 있다. 타입스크립트 역시 이에 대응하는 타입으로 {}
를 사용할 수 있는데 자바스크립트와 마찬가지로 빈 객체임을 의미한다. 따라서 {}
타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없다. 사실 빈 객체 타입을 지정하기 위해서는 {}
보다 유틸리티 타입으로 Record<string, never>
처럼 사용하는게 바람직하다.
객체를 타이핑하기 위해 자주 사용하는 키워드로 type
과 interface
가 있다.
팀 내에서
type
이나interface
만을 써야하는 상황이 있었나요?
- 배달이 객체 지향적으로 코드를 짤 때, 특히 상속하는 경우에는
interface
를 사용했던 것 같음. 예를 들어extends
나implements
를 사용할 때- 냥이 유니온 타입이나 교차 타입 등 type 정의에서만 쓸 수 있는 기능을 활용할 때
type
을 사용함.interface
키워드는 예를 들어 다이얼로그 컴포넌트를 만들 때, 사이즈가 다른 다이얼로그끼리 같은 속성을 공유하는 기준 인터페이스를 정의하고 확장할 때 사용함
any
타입을 어쩔 수 없이 사용해야할 때개발 단계에서 임시로 값을 지정해야할 때
매우 복잡한 구성 요소로 이루어진 개발 과정에서 추후 값이 변경될 가능성이 있거나 아직 세부 항목에 대한 타입이 확정되지 않은 경우가 생길 수 있음. 이럴 때 값을 any
로 지정하면 타입을 세세하게 명시하는 데 소요되는 시간을 절약할 수 있음
어떤 값을 발아올지 또는 넘겨줄지 정할 수 없을 때
값을 예측할 수 없을 때 암묵적으로 사용
외부 라이브러리나 웹 API의 요청에 따라 다양한 값을 반환하는 API가 존재할 수 있음. 예를 들어, Fetch API의 일부 메서드는 요청 이후의 응답을 특정 포맷으로 파싱하는데 이 때 반환 타입이 any
로 매핑되어 있는 것을 볼 수 있음
unknown
타입은 any
타입과 유사하게 모든 타입의 값이 할당될 수 있다. 그러나 any
를 제외한 다른 타입으로 선언된 변수에는 unknown
타입 값을 할당할 수 없다.
any | unknown |
---|---|
어떤 타입이든 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
을 사용함
값을 반환할 수 없는 타입을 말함
function generateError(res:Response):never{
throw new Error(res.getMessage());
}
useState
는 튜플 (!! 그러고보니)
열거형에 사용할 때는 주의해야할 점이 있다. 먼저 숫자로만 이루어져 있거나 타입스크립트가 자동으로 추론한 열거형은 안전하지 않은 결과를 낳을 수 있다. 맨 처음 예시를 보면 역방향으로도 접근할 수 있음을 보여준다. 여기서 할당된 값을 넘어서는 범위로 역방향으로 접근하더라도 타입스크립트는 막지 않는다. 이러한 동작을 막기 위해 const enum
으로 열거형을 선언하는 방법이 있다. 이 방식은 역방향으로의 접근을 허용하지 않기 때문에 자바스크립트에서의 객체에 접근하는 것과 유사한 동작을 보장한다.
그러나 const enum
으로 열거형을 선언하더라도 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못한다. 문자열 상수 방식으로 열거형을 사용하는 것이 숫자 상수 방식보다 더 안전하며 의도하지 않은 값의 할당이나 접근을 방지하는 데 도움이 된다.
열거형은 타입스크립트 코드가 자바스크립트로 변환될 때 즉시 실행함수(IIFE
) 형식으로 변환되는 것을 볼수 있다.
type ProductItem = {
id:number;
name:string;
type:string;
}
type ProductItemWithDiscount = ProductItem & {discountAmount: number};
특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용하는 문법이다. 인터페이스 내부에 [Key: K]: T
꼴로 타입을 명시해주면 되는데 이는 해당 타입의 속성 키는 모두 K 타입이어야하고 속성값은 모두 T 타입을 가져야한다는 의미다.