*트리쉐이킹(tree-shaking): 자바스크립트, 타입스크립트에서 사용하지 않는 코드를 삭제하는 방식. 나무를 흔들면 죽은 나뭇잎이 떨어지는 모습을 보고 이름을 따왔다고 한다. ES6 이후의 최신 어플리케이션 개발 환경에서는 웹팩, 롤업 같은 모듈 번들러를 사용한다. 이러한 도구로 번들링 작업을 수행할 때, 사용하지 않는 코드는 자동으로 삭제된다. CommonJS 모듈은 트리쉐이킹을 지원하지 않지만 ES6 이후에는 파일 내 특정 모듈만 임포트하면 해당 모듈을 사용하지 않는 파일 코드는 삭제되어 더 작은 크기의 번들링 파일을 생성할 수 있게 되었다
typeof, instanceof, 타입 단언을 사용해 타입을 확인할 수 있음
typeof는 연산하기 전에 피연산자의 데이터 타입을 나타내는 문자열을 반환함 -> typeof 연산자가 반환하는 값은 자바스크립트의 7가지 기본 데이터 타입(Boolean, null, undefined, Number, BigInt, String, Symobl)과 Function(함수), 호스트 객체, object객체가 될 수 있음
typeof 2022; // number
typeof "woowahan"; // string
typeof ture; // boolean
typeof {}; // object
타입스크립트에는 값 공간과 타입 공간이 별도로 존재 -> 타입스크립트에서 typeof 연산자도 값에서 쓰일 때와 타입에서 쓰일 때의 역할이 다름
interface Person {
first: string;
last: string;
}
const person: Person = { first: "zig", las: "song"};
function email(options: { person: Person; subject: string; body: string}) {}
값에서 사용된 typeof 연산자는 자바스크립트 typeof 연산자와 동일하게 해당 타입을 string 형태로 나타냄 (자바스크립트 런타임의 typeof 연산자가 된다)
const v1 = typeof person; // 값은 'object'
const v2 = typeof email; // 값은 'function'
const user = { name: 'lee', age: 20 };
console.log(typeof user); // 'objcet'
console.log(typeof user.age); // 'number'
타입에서 사용된 typeof는 값을 읽고 타입스크립트 타입을 반환함 (typeof 연산자는 해당 타입스크립트를 반환)
type T1 = typeof person; // 타입은 Person
type T2 = typeof email; // 타입은 (options: { person: Person; subject: stirng; body: stirng; }) => void
const user = { name: 'lee', age: 20 };
type UserType = typeof user; // {name: string; age: number}
person 변수는 interface Person 타입으로 선언되었기 때문에 타입 공간에서의 typeof person의 Person을 반환한다.
email 함수는 타입 공간에서 typeof 연산자로 값을 읽을 때, 함수의 매개변수 타입과 리턴 타입을 포함한 함수 시그니처 타입을 반환한다.
v1과 v2는 const 키워드로 선언된 변수로 값이 할당될 공간이다. 값 공간의 typeof는 피연산자인 person과 email의 런타임 타입을 가리키는 문자열을 반환한다 => 값에서 사용된 typeof 연산자는 자바스크립트 typeof 연산자와 동일하게 동작
자바스크립트 클래스는 typeof 연산자를 쓸 때 주의해야 한다. 앞서 선언한 Developer 클래스를 다시보면,
class Developer {
name: string;
sleepingTime: number;
constructor(name: string, sleepingTime: number){
this.name = name;
this.sleepingTime = sleepingTime;
}
}
const d = typeof Developer; // 값이 'function'
type T = typeof Developer; // 타입이 typeof Developer
자바스크립트의 클래스는 결국 함수이므로 값 공간에서 typeof Developer의 값은 function
타입 공간에서 typeof Developer의 반환값은 type T에 할당된 Developer는 인스턴스 타입이 아니라 new 키워드를 사용할 때 볼 수 있는 생성자 함수이기 때문
const zig: Developer = new Developer("zig", 7);
type ZigType = typeof zig; // 타입이 Developer
Developer 클래스로 생성한 zig 인스턴스는 Developer가 인스턴스 타입으로 생성되었기 때문에 타입 공간에서의 typeof zig 즉, type ZigTtype은 Developer를 반환함
그러나 Developer는 Developer 타입의 인스턴스를 만드는 생성자 함수 -> typeof Developer 타입도 그 자체인 typeof Developer가 됨
typeof Developer를 풀어서 설명하면,
new (name: string, sleepingtime: number): Developer
자바스크립트에서 instanceof 연산자를 사용하면 프로토타입 체이닝 어딘가에 생성자의 프로토타입 속성이 존재하는지 판단할 수 있음 -> typeof 연산자처럼 istanceof 연산자의 필터링으로 타입이 보장된 상태에서 안전하게 값의 타입을 정제하여 사용할 수 있음
let error = unknown;
if(error instanceof Error) {
showAlertModal(error.message);
} else {
throw Error(error);
}
타입스크립트에서는 타입 단언이라 부르는 문법을 사용해 타입을 강제할 수도 있음 : 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과 undefined를 제외한 모든 원시 값은 해당 원시 값을 래핑한 객체를 가진다.
원시 래퍼 객체는 이름에서 알 수 있듯이 원시 값이 아닌 객체라는 점에 주의하자. 따라서 타입스크립트에서는 내장 원시 타입에 해당하는 타입을 파스칼 표기법으로 쓰지 않도록 주의해야 한다. 타입스크립트에도 원시 래퍼 객체가 존재하는데 이것은 고유한 타입으로 분류되기 때문에 둘은 엄연히 다르다.
const isEmpty: boolean = true;
const isLoading: boolean = false;
// errorAction.type과 ERROR_TEXT가 같은지 비교한 결괏값을 boolean 타입으로 반환하는 함수
function isTextError(errorCode: ErrorCodeType): boolean {
const errorAction = getErrorAction(errorCode);
if(errorAction) {
return errorAction.type === ERROR_TEXT;
}
return false;
}
let value: string;
console.log(value); // undefined (값이 아직 할당되지 않음)
type Person = {
name: string;
job?: string;
};
let value: null | undefined;
console.log(value); // undefined(값이 아직 할당되지 않음)
value = null;
console.log(value); // null
type Person1 = {
name: string;
job?: string;
}
type Person2 = {
name: string;
job: string | null;
}
const maxLegnth: number = 10;
const maxWidth: number = 120.3;
const maximum: number = +Infinity;
const notANumber: number = NaN;
const bigNumber1: bigint = BigInt(999999999999);
cosnt bigNumber2: bigInt = 99999999999;
const receiverName: string = "kg";
const receiverPhoneNumber: string = "010-0000-0000";
const letterContent: string = `안녕, 내이름은 ${senderName}이야.`;
const MOVIE_TITLE = Symbol("title");
const MUSIC_TITLE = Symbol("title");
console.log(MOVIE_TITLE === MUSIC_TITLE); // false
let SYMBOL: unique symbol = Symbol(); // A variable whose type is a 'unique symbol' type must be 'const'
number, string, boolean은 가장 대표적인 3가지 원시 타입이자 다른 타입을 구성하는 기본 원소 같은 타입으로 가장 많이 사용된다. 따라서 사용자에 따라 다르게 사용될 여지가 적다. 반면, null이나 undefined는 tsconfig 옵션이나 사용자 취향에 따라 다르게 사용될 여지가 있다.
타입스크립트의 모든 타입은 기본적으로 null과 undefined를 포함하고 있다. 하지만 tsconfig의 strictNullChecks 옵션을 활성화했을 때는 사용자가 명시적으로 해당 타입에 null 이나 undefined를 포함해야만 null과 undefined를 사용할 수 있다. 그렇지 않으면 null과 undefined가 될 수 있는 경우에 타입스크립트 에러가 발생하는데 보통 타입 가드로 null과 undefined가 되는 경우를 걸러낸다.
!연산자를 사용해 타입을 단언하는 방법도 있다.
이를 통해 사용자는 해당 참조가 null이나 undefined가 아니라고 보장할 수 있는데, 일반적으로 타입 가드를 사용하는 것이 더 안전하다고 여겨져 단언문보다 타입 가드가 좀더 선호되는 경향이 있다.
function isObject(value: object) {
return(
Object.prototype.toString.call(value).replace(/\[|\]|\s|object/g, "") === "Object");
}
// 객체, 배열, 정규 표현식, 함수, 클래스 등 모두 obejct 타입과 호환됨
isObject({});
isObject({ name: "KG" });
isObject([0, 1, 2]);
isObject(new RegExp("object"));
isObject(functon () {
console.log("hello world");
});
isObject(class Class {});
// 그러나 원시 타입은 호환되지 않음
isObject(20); // false
isObject("KG"); // false
// 정상
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
};
// SyntaxError
const noticePopup: { title: string; description: string } = {
title: "IE 지원 종료 안내",
description: "2022.07.15일부로 배민상회 IE 브라우저 지원을 종료합니다.",
startAt: "2022.07.15 10:00:00", // startAt은 지정한 타입에 존재하지 않으므로 오류
};
let noticePopup: {} = {};
noticePopup.title = "IE 지원 종료 안내"; // (X) title 속성을 지정할 수 없음
console.log(noticePopup.toString()); // [object Object]
타입스크립트는 자바스크립트 객체를 세분화해서 타입을 지정할 수 있는 타입 시스템을 갖음
자바스크립트에서는 흔히 사용하는 객체 자료구조 외에 배열, 함수, 정규식 등이 객체 범주에 속함
타입스크립트에서는 이런 각각의 객체에 타입을 지정할 수 있음
자바스크립트의 배열 자료구조는 원소를 자유롭게 추가하고 제거할 수 있으며 타입 제한 없이 다양한 값을 다룸 -> 하나의 배열 안에 숫자, 문자열과 같은 서로 다른 값이 혼재될 수 있음 -> 이러한 쓰임은 타입스크립트가 추구하는 정적 타이핑 방향과 맞지 않음
타입스크립트에서는 배열을 array라는 별도 타입으로 다룸
타입스크립트 배열 타입은 하나의 타입 값만 가질 수 있다는 점에서 자바스크립트 배열보다 조금 더 엄격함 but 자바스크립트와 마찬가지로 원소 개수는 타입에 영향 주지 않음
타입스크립트에서 배열 타입을 선언하는 방식은 Array 키워드로 선언하거나 대괄호([])를 사용해서 선언하는 방법이 있음
const getCartList = async (cartId: number[]) => {
const res = await CartApi.GET_CART_LIST(cartId);
return res.getData();
};
getCartList([]); // O 빈 배열도 가능
getCartList([1001]); // O
getCartList([1001, 1002, 1003]); // O number 타입 원소 몇 개가 들어와도 상관 없음
getCartList([1001, "1002"]);// X "1002"는 string 타입이므로 불가함
주의해야할점은 튜플 타입도 대괄호로 선언한다는 것
타입스크립트 튜플 타입은 배열과 유사하지만 튜플의 대괄호 내부에는 선언 시점에 지정해준 타입 값만 할당할 수 있음 & 원소 개수도 타입 선언 시점에 미리 정해짐 -> 객체 리터럴에서 선언하지 않은 속성을 할당하거나, 선언한 속성을 할당하지 않을 때 에러가 발생한다는 점과 비슷
const targetCodes: ["CATEGORY", "EXHIBITION"] = ["CATEGORY", "EXHIBITION"]; // O
const targetCodes: ["CATEGORY", "EXHIBITION"] = [
"CATEGORY",
"EXHIBITION",
"SALE"
]; // X SALE은 지정할 수 없음
타입스크립트 object 타입은 실무에서는 잘 사용하지 않음(자바스크립트와 대응되는 예시를 보여주기 위해 언급한 것이라고함)
객체를 타이핑하기 위해서는 타입스크립트에서만 독자적으로 사용할 수 있는 키워드를 사용하는게 일반적
객체 범주에 속하는 배열도 마찬가지로 타입스크립트에서는 object 대신 오직 배열임을 나타내는 타입스크립트만의 배열 타입 시스템을 사용할 수 있음
흔히 객체를 타이핑 하기 위해 자주 사용하는 키워드로 type과 interface가 있음
중괄호를 사용한 객체 리터럴 방식으로 타입을 매번 일일이 지정하기에는 중복적인 요소가 많음 -> 반복적으로 사용돼도 중복 없이 해당 타입을 쓸 수 있음
type NoticePopupType = {
title: string;
description: string;
}
interface INoticePopup {
title: string;
description: string;
}
const noticePopup1: NoticePopupType = { ... };
const noticePopup2: INoticePopup = { ... };
타입스크립트에서 일반적으로 변수 타입을 명시적으로 선언하지 않아도 컴파일러가 자동으로 타입을 추론함 -> 타입스크립트 컴파일러가 변수 사용 방식과 할당된 값의 타입을 분석해서 타입을 유추한다는 것을 의미 -> 모든 변수에 타입을 일일이 명시적으로 선언할 필요는 없음 그러나 타입 추론에 대한 다양한 의견이 있음 -> 컴파일러에 타입 추론을 온전히 맡길 것인지 명시적으로 타입을 선언할 것인지는 개인의 취향 또는 팀의 컨벤션에 따라 다를 수 있음
type과 interface를 둘 다 쓸 수 있는 상호아에서 주로 어떤 것을 사용하나요?
A: 정해진 컨벤션이 있는 건 아니지만 대부분의 상황에서 interface를 사용한다. 하지만 간단한 용도로는 type도 사용한다. 만약 컨벤션으로 정한다면 공식 문서에 쓰인 내용을 바탕으로 전역적으로 사용할 때는 interface를, 작은 범위 내에서 한정적으로 사용한다면 type를 써도 되지 않을까 한다.
B: 둘 다 사용하는데 type은 어떤 값에 대한 정의 같이 정적으로 결정되어 있는 것을, interface는 확장될 수 있는 basis를 정의하거나 어떤 object 구성을 설명하는 요소라 생각한다.
C: 필요에 따라 type 정의와 interface 모두 사용한다. 예를 들어 선언 병합이 필요할 때는 interface를 사용하고, computed value를 사용해야 한다면 type 정의를 쓴다.
D: interface의 필요성을 느끼지 못하고 있어서 type 정의를 주로 사용한다. 현재는 아니지만 예전에 디버깅할 때 IDE에서 inteface는 인터페이스 이름만 노출되고, type은 리터럴한 값이 직접 노출되었다. 그래서 type을 사용하면 더 쉽게 타입 추론을 할 수 있어서 선호했다.
팀 내에서 type이나 interface만을 써야하는 상황이 있었나요?
A: 객체 지향적을 코드를 짤 때, 특히 상속하는 경우에 interface를 사용했다. 예를 들어, extends나 implements를 사용할 때.
B: 유니온 타입이나 교차 타입 등 type 정의에서만 쓸 수 있는 기능을 활용할 때 type을 활용했다. interface 키워드는 예를 들어 다이얼로그(Dialog) 컴포넌트를 만들때, 사이즈가 다른 다이얼로그끼리 같은 속성을 공유하는 기준 인터페이스를 정의하고 확장할 때 사용했다.
C: props에 Record 형식을 extends할 때 interface로 선언된 변수를 넣으면 에러가 발생해서 type으로 바꿔 넣은 경험이 있다. 예를 들어 표 컴포넌트를 만들어서 쓸 때, 배열로 되어있는 데이터를 넣으면 피드 이름이 특정되지 않는 경우가 있다. 이럴 때 Record로 선언해서 컴포넌트를 사용했는데 interface처럼 인덱스 키가 따로 설정되지 않으면 오류가 발생했었다.
D: computed value를 써야할 때 type 정의를 사용했다.
function add(a, b) {
return a + b;
}
console.log(typeof add); // 'function'
function add(a: number, b: number): number {
return a + b;
}
매개변수 a, b에 number 타입이 지정되고, 함수 이름 옆에 콜론과 함께 다시 number 타입으로 반환 값을 지정하고 있음 -> 타입스크립트에서 함수를 작성할 때 매개변수와 반환 값에 대한 타입을 지정하는 문법을 설명
그럼 함수 자체의 타입은 어떻게 지정할 수 있을까? => 호출 시그니처를 정의하는 방식을 사용하면 됨
*호출 시그니처(call signature): 타입스크립트에서 함수 타입을 정의할 때 사용하는 문법. 함수 타입은 해당 함수가 받는 매개변수와 반환하는 값의 타입으로 결정된다. 호출 시그니처는 이러한 함수의 매개변수와 반환 값의 타입을 명시하는 역할을 함
type add = (a: number, b: number) => number;