자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있다.
자바스크립트의 동적 타이핑으로 돌아가는 것과 비슷한 결과를 가져오기 때문에 지양해야 할 패턴으로 알려져 있다. 하지만 어쩔 수 없이 any 타입을 사용해야하는 경우도 있다.
any 타입과 유사하게 모든 타입의 값이 할당될 수 있다. 그러나 any를 제회한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없다.
any | unknown |
---|---|
- 어떤 타입이든 any 타입에 할당 가능 | |
- any 타입은 어떤 타입으로도 할당 가능 (단 never는 제외) | - 어떤 타입이든 unknown 타입에 할당 가능 |
- unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능 |
let unknownValue: unknown;
unknownValue = 100;
unknownValue = 'hello world';
unknownValue = () => console.log("this is any type");
let someValue1: any = unknownValue;
let someValue2: number = unknownValue; // x
let someValue3: string = unknownValue; // x
아무런 값을 반환하지 않는 경우에 사용한다.
주로 함수 반환타입으로 사용되지만 사실 함수에 국한된 타입은 아니고 변수에도 할당 할 수 있지만 함수가 아닌 값에 대해서는 대부분 무의미하다. void 타입으로 지정된 변수는 undefined 또는 null 값만 할당할 수 있다. 하지만 tsconfig.json에서 strictNull-Checks 옵션이 설정되어 있다면 null값을 할당할 수 없다.
let voidValue: void = undefined;
// strictNullCheckes가 비활성화 된 경우에 가능
voidValue = null;
값을 반환할 수 없는 타입을 말한다.
자바스크립트에서도 Object.prototype.toString.call(…) 연산자를 사요아여 확인할 수 있다.
자바스크립트에서는 배열을 객체에 속하는 타입으로 분류한다. 즉 자바스크립트에서는 배열을 단독으로 배열이라는 자료형에 국한하지 않는다.
자바스크립트의 배열은 동적 언어의 특징에 따라 어떤 값이든 배열의 원소로 허용한다. 하지만 이런 개념은 타입스크립트의 정적 타이핑과 잘 부합하지 않는다.
const array: number[] = [1, 2, 3];
const array: Array<numbe> = [1, 2, 3];
위 두가지 방식으로 배열 타입을 선언할 수 있으며 차이점은 선언하는 형식 외에는 없다.
Array 키워드 외에도 대괄호([])를 사용해 직접 타입을 명시할 수도 있는데, 이때의 타입은 배열보다 좁은 범위인 튜플을 가리킨다. 튜플은 대괄호 안에 선언하는 타입의 개수가 튜플이 가질 수 있는 원소의 개수를 가진다. 길이까지 제한한다고 볼 수 있다.
let tuple:[number] = [1];
tuple = [1, 2]; // 불가능
tuple = [1, "string"]; // 불가능
열거형이라고도 불리는 구조체를 만드는 타입 시스템이다. 기본적인 추론 방식을 숫자 0부터 1씩 늘려가며 값을 할당하는 것이다.
enum ProgrammingLanguage {
Typescript, // 0
Javascript, // 1
Java, // 2
Python, // 3
Kotlin, // 4
}
ProgrammingLanguage.Kotlin; // 4
ProgrammingLanguage[2] // "Java"
열거형을 사용할 때는 주의해야 할 점도 있는데, 먼저 숫자로만 이루어져 있거나 타입스크립트가 자동으로 추론한 열거형은 안전하지 않은 결과를 낳을 수 있다. 위 예시를 보면 역방향으로도 접근할 수 있음을 보여준다. 이러한 동작을 막기 위해 const enum으로 열거형을 선언하는 방법이 있다.
그러나 const enum으로 열거형을 선언하더라고 숫자 상수로 관리되는 열거형은 선언한 값 이외의 값을 할당하거나 접근할 때 이를 방지하지 못한다. 따라서 문자열 상수 방식으로 열거형을 사용하는 것이 숫자 상수 방식보다 더 안전하며 의도하지 않은 값의 할당이나 접근을 방지하는 데 도움이 된다.
const enum NUMBER {
ONE = 1,
TWO = 2,
}
const myNumber: NUMBER = 100; // 에러가 나지 않음
const enum STRING_NUMBER {
ONE = "ONE",
TWO = "TWO",
}
const myStringNumber: STRING_NUMBER = "THREE"; // 에러 발생
열거형은 타입스크립트 코드가 자바스크립트로 변환될 때 즉시 실행 함수 형식으로 변환되는 것을 볼 수 있다.
이때 일부 번들러에서 트리쉐이킹 과정 중 즉시 실행 함수로 변환된 값을 사용하지 않는 코드로 인식하지 못하는 경우가 발행할 수 있다. 따라서 불필요한 코드의 크기가 증가하는 결과를 초래할 수 있다. 이를 해결하기 위해서 앞서 언급했던 const enum 또는 as const assertion을 사용해서 유니온 타입으로 열거형과 동일한 효과를 얻는 방법이 있다.
💡 즉시 실행 함수 : 말 그대로 선언과 동시에 실행되는 함수
💡 번들러 : 자바스크립트 파일과 관련된 자산들(HTML, CSS, 이미지 등)을 하나의 파일 또는 몇 개의 파일로 묶어주는 도구
💡 트리쉐이킹 : 모듈 번들링 과정에서 사용되지 않는 코드를 제거하여 최종 번들 크기를 줄이는 최적화 기법
교차 타입을 사용하면 여러 가지 타입을 결합하여 하나의 단일 타입으로 만들 수 있다. 교차 타입은 &을 사용해서 표기한다.
type ProductItme = {
id: number;
name: string;
type: string;
price: number;
imageUrl: string;
quantity: number;
};
type ProductItemWithDiscount = ProductItem & { discountAmount: number };
교차 타입이 타입 A와 B를 모두 만족하는 경우라면, 유니온 타입은 타입A 또는 B 중 하나가 될 수 있는 타입을 말하며 A | B와 같이 표기한다.
type CardItem = {
id: number;
name: string;
type: string;
imageUrl: string;
};
type PromotionEventItem = ProductItem | CardItem;
const printPromotionItem = (item: PromotionEventItem) => {
console.log(item.name); // O
console.log(item.quantity); // 컴파일 에러 발생
}
printPromotionItem 함수를 보면 인자로 PromotionEventItem 타입을 받고 있다 해당 함수 내부에서 quantity를 참조하려고 시도하면 컴파일 에러가 발생하는데, 이는 quantity가 ProductItem에만 존재하기 때문이다.
특정 타입의 속성 이름은 알 수 없지만 속성값의 타입을 알고 있을 때 사용한다.
interface IndexSignatureEx {
[key: string]: number;
}
다른 속성을 추가로 명시해줄 수 있는데 이때 추가로 명시된 속성은 인덱스 시그니처에 포함되는 타입이어야 한다.
interface IndexSignatureEx {
[key: string]: number | boolean;
length: number;
isValid: boolean;
name: string; // 에러 발생
다른 타입의 특정 속성이 가지는 타입을 조회하기 위해 사용된다. 인덱스에 사용되는 타입 또한 그 자체로 타입이기 때문에 유니온 타입, keyof, 타입 별칭 등의 표현을 사용할 수 있다.
type Example = {
a: number;
b: string;
c: boolean;
};
type IndexedAccess = Example["a"];
type IndexedAccess2 = Example["a" | "b"]; // number | string
type IndexedAccess3 = Example[keyof Example]; // number | string | boolean
또한 배열의 요소 타입을 조회하기 위해 인덱스드 엑세스 타입을 사용하는 경우가 있다.
const PromotionList = [
{ type: "product", name: "chicken" },
{ type: "product", name: "pizza" },
{ type: "card", name: "cheer-up" },
];
type ElementOf<T> = typeof T[number];
// type PromotinoItemType = { type: string; name: string }
type PromotinoItemType = ElementOf<PromotionList>;
다른 타입을 기반으로 한 타입을 선언할 때 사용하는 문법이다.
type Example = {
a = number;
b: string;
c: boolean;
};
type Subset<T> = {
[K in keyof T]?: T[K]
};
const aExample: Subset<Example> = { a:3 };
const bExample: Subset<Example> = { b:"hello" };
맵드 타입에서 매핑할 때는 readonly와 ?를 수식어로 적용할 수 있다. 기존 타입에 존재하던 readonly나 ? 앞에 -를 붙여주면 해당 수식어를 제거한 타입을 선언할 수 있다.
type ReadOnlyEx = {
readonly a:number;
readonly b: string;
};
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type RedultType = CreateMytable<ReadOnlyEx>; // { a: number; b: string }
템플릿 리터럴 문자열을 사용하여 문자열 리터럴 타입을 선언할 수 있는 문법이다.
type Stage = "init" | "select-image" | "edit-image";
type StageName = `${Stage}-stage`; // 'init-stage' | 'select-image-stage' | 'edit-image-stage'
정적 언어에서 다양한 타입 간에 재사용성을 높이기 위해 사용하는 문법이다.
조금 더 자세하게는 함수, 타입, 클래스 등에서 내부적으로 사용할 타입을 미리 정해두지 않고 타입 변수를 사용해서 해당 위치를 비워 둔 다음, 실제로 그 값을 사용할 때 외부에서 타입 변수 자리에 타입을 지정하여 사용하는 방식을 말한다.
type ExampleArrayType<T> = T[];
const array1: ExampleArrayType<string> = ["치킨", "피자", "우동"];
제네릭 함수를 호출할 때 반드시 꺾쇠괄호 안에 타입을 명시해야하는 것은 아니다. 컴파일러가 인수를 보고 타입을 추론해준다.
function exampleFunc<T>(arg: T): T[] {
return new Array(3).fill(arg);
}
exampleFunc("hellop"); // T는 string으로 추론된다.
제네릭을 사용할 때 주의해야 할 점이 있다. 파일 확장자가 tsx일 때 화살표 함수에 제네릭을 사용하면 에러가 발생한다. 제네릭의 꺾쇠괄호와 태그의 꺾쇠괄호를 혼동하여 생기는 문제이다. 이러한 상황을 피하기 위해서는 제네릭 부분에 extends 키워드를 사용하여 컴파일러에게 특정 타입의 하위 타입만 올 수 있음을 확실히 알려주면 된다.
const arrowExampleFunc = <T>(arg: T): T[] => {
return new Array(3).fill(arg);
}
const arrowExampleFunc2 = <T extends {}>(arg: T): T[] => {
return new Array(3).fill(arg);
}
어떤 함수의 매개변수나 반환 값에 다양한 타입을 넣고 싶을 때 사용할 수 있다.
function identity<T>(arg: T): T {
return arg;
}
함수 타입 문법으로 함수의 매개변수와 반환 타입을 미리 선언하는 것을 말한다.
type AddFunction = (a: number, b: number) => number;
const add: AddFunction = (a, b) => {
return a + b;
};
console.log(add(2, 3)); // 5
외부에서 입력된 타입을 클래스 내부에 적용할 수 있는 클래스이다.
// 제네릭 클래스 정의
class Box<T> {
private content: T;
constructor(content: T) {
this.content = content;
}
getContent(): T {
return this.content;
}
setContent(content: T): void {
this.content = content;
}
}
// 제네릭 클래스 인스턴스 생성
const numberBox = new Box<number>(123);
console.log(numberBox.getContent()); // 123
const stringBox = new Box<string>("Hello");
console.log(stringBox.getContent()); // Hello
제한된 제네릭은 타입 매개변수에 대한 제약 조건을 설정하는 기능을 말한다.
type AnimalSound<Animal extends 'cat' | 'dog'> =
Animal extends 'cat' ? 'meow' :
Animal extends 'dog' ? 'woof' :
never;
제네릭은 여러 타입을 상속받을 . 수있으며 타입 매개변수를 여러 개 둘 수도 있다.
type Trio<T, U, V> = {
first: T;
second: U;
third: V;
};
function printTrio<T, U, V>(trio: Trio<T, U, V>): void {
console.log(`(${trio.first}, ${trio.second}, ${trio.third})`);
}
// 예시 사용
let trio1: Trio<number, string, boolean> = { first: 1, second: 'hello', third: true };
printTrio(trio1); // 출력: (1, hello, true)
let trio2: Trio<string, Date, number> = { first: 'today', second: new Date(), third: 42 };
printTrio(trio2); // 출력: (today, [current date and time], 42)
현업에서 가장 많이 제네릭이 활용될 때는 API 응 답 값의 타입을 지정할 때이다.
type GType<T> = T;
type RequirementType = "USE" | "UN_USE" | "NON_SELECT";
interface Order {
getRequirement(): GType<RequirementType>;
}
type RequirementType = "USE" | "UN_USE" | "NON_SELECT";
interface Order {
getRequirement(): RequirementType;
}