enum Animal {
Cat,
Dog,
Rabbit,
}
enum은 상수들의 집합을 정의할 때 사용한다. TypeScript에서는 숫자 열거형과 문자열 열거형을 지원하며 임의의 숫자나 문자열을 할당할 수 있다.
enum을 사용하면 코드의 가독성 향상 시키고 휴먼에러를 방지할 수 있다.
enum Animal {
Cat = 1,
Dog = 5,
Rabbit = 6
}
let test1: Animal = Animal.Cat;
let test2: number = Animal.Rabbit;
console.log(test1); // 1
console.log(test2); // 6
enum은 기본값으로 숫자 열거형을 사용하며 Animal에 정의된 각 멤버들은 순차적으로 0부터 1씩 증가하는 값을 가진다.
직접 number를 지정할 수도 있고 이후의 값은 자동으로 enum에 선언된 순서에 따라 1씩 증가한다.
enum Animal {
Cat = 'luna',
Dog = 'dangdang',
Rabbit = 'toto'
}
let test: Animal = Animal.Dog;
console.log(test); // dangdang
Animal에 정의된 멤버들에 문자열 값이 할당되어있다. 문자열 열거형은 숫자 열거형과 거의 비슷하지만 자동으로 증가시키는 기능은 없다.
enum Test {
A
}
let a = Test.A;
let nameOfA = Test[a]; // A
숫자 열거형에만 존재하는 특징으로 열거형의 키(key)로 값(value)을 얻을 수 있고 값(value)으로 키(key)를 얻을 수 있다.
트리쉐이킹(사용하지 않는 코드를 제거) 되지 않는다.
숫자 열거형은 예기치 못한 오류가 발생할 수 있다.
Enum간 값 비교가 되지않는다.
-> Union Type 사용
제네릭은 Java, C# 등 정적 언어에서 여러 타입을 받아 재사용 가능한 컴포넌트를 생성할 때 주로 사용한다.
TypeScript에서 함수나 클래스를 정의할 때 매개변수 타입과 리턴 타입을 지정해야한다. 이때 제네릭을 사용하면 타입을 변수화하여 타입을 미리 지정(고정)하지 않고 함수나 클래스를 호출할 때 전달되는 데이터의 타입에 따라 자동으로 타입을 추론한다.
-> 선언 시점이 아닌 호출 시점에 타입이 결정되며 다양한 타입을 받아 재사용된다.
function print(text: string): string {
return text;
}
print('test'); // test
print(1234); // Argument of type 'number' is not assignable to parameter of type 'string'.
위 예시는 제네릭을 사용하지 않고 고정된 타입 지정하였다. print 함수에 특정 타입을 지정해야 하고 string 타입 외에 다른 타입이 전달되면 컴파일 에러가 발생한다.
function print(text: string): string
function print(text: number): number
function print(text: any): any {
return text
}
print('test'); // test
print(1234); // 1234
string 외에 다른 타입의 데이터를 받기 위해 함수를 중복으로 선언하여 문제를 해결하였지만 타입이 많아질수록 코드량도 많아지기 때문에 이 방법은 가독성과 유지보수에 좋지 않다.
함수 오버로딩 - 동일한 함수의 매개변수 타입과 리턴 타입을 다르게 하여 다양하게 사용
function print(text: string | number) {
return text;
}
print('test');
print(1234);
function add(x: number | string, y: number | string) {
return x + y // Operator '+' cannot be applied to types 'string | number' and 'string | number'.
}
유니온 타입을 사용하여 두가지 타입을 받을 수 있게 되었다.
하지만 string, number 타입 둘 다 접근할 수 있기 때문에 타입 추론이 불가능해지고 에러가 발생한다.
function print(text: any): any {
return text;
}
any 타입은 여러가지 타입을 허용하지만 타입 검사를 하지 않기 때문에 반환되는 타입을 추론할 수 없게된다.
// 인터페이스를 통해 제네릭 함수 타입 정의
interface Generic {
<T>(text: T): T;
}
function printText<T>(text: T): T {
return text;
}
// 화살표 함수로 표기
const printText = <T>(text: T): T => {
return text;
}
let test: Generic = printText;
test<number>(1234);
test<string[]>(['a', 'b', 'c']);
printText 함수 타입으로 <T>
, text 매개변수 타입으로 T
, 리턴 타입으로 T
변수(타입)를 추가했다. 제네릭을 통해 타입을 변수화하고 함수 호출 시 전달되는 데이터의 타입에 따라 타입을 결정하는 방식으로 다양한 타입을 안정적으로 재사용할 수 있다.
-> any 타입과 다르게 반환되는 타입을 정확하게 추론할 수 있다.
호출 시 해당 함수 뒤 <>
안에 타입을 명시한다.
interface Generic<T> {
(text: T): T;
}
function printText<T>(text: T): T {
return text;
}
let textNum: Generic<number> = printText;
let textStr: Generic<string> = printText;
textNum(1234);
textStr('test');
변수에 함수를 할당할 때 인터페이스를 통해 제네릭 타입을 결정하는 방식도 가능하다.
let text1 = printText<number>(1234);
let text2 = printText(1234);
두가지 호출 방법 중 text2 방법의 코드가 더 짧고 가독성이 좋기 때문에 많이 사용되나 코드가 복잡해져서 타입 추론이 불가능한 경우 <>
안에 타입을 명시한다.
interface Person<T> {
name: T; // 제네릭 타입, name 프로퍼티에 다양한 타입의 데이터가 들어올 경우
age: number;
gender: string;
}
let user1: Person<string> = {
name: 'kim',
age: 25,
gender: 'female'
};
let user2: Person<number> = {
name: 100,
age: 20,
gender: 'male'
};
Person 인터페이스에 <T>
변수를 선언하고 제네릭을 사용할 프로퍼티에 제네릭 타입 T
를 선언한다.
Person 인터페이스를 타입으로 사용하는 user1, user2 객체의 <>
안에 사용할 타입을 선언한다. -> <>
안에 타입을 명시하지 않아도 컴파일러가 알아서 타입을 추론하지만 코드가 복잡하거나 유니온 타입을 사용할 경우 제네릭을 명시하는 것이 좋다.
name 프로퍼티에 선언했던 타입의 값을 넣어주면 인터페이스를 여러개 만들 필요없이 재사용할 수 있게된다.
let user3: Person<{ str: string; num: number; }> = {
name: { str: 'park', num: 1234 },
age: 10,
gender: 'male'
};
제네릭에 객체 리터럴 형태의 타입도 할당할 수 있다.
type Person<T> = {
name: string;
age: T;
}
let user: Person<number> = {
name: 'kim',
age: 30
}
type Test<T> = T[] | T;
let strArr: Test<string> = ['one', 'two', 'three'];
let numARR: Test<number> = [1, 2, 3];
let str: Test<string> = 'test';
let num: Test<number> = 1234;
function print<T>(text: T): T {
console.log(text.length); // Property 'length' does not exist on type 'T'.
return text;
}
위 예시는 제네릭 T
에 어떤 타입이 들어올지 모르기 때문에(number 타입이 들어온다면 .length를 사용할 수 없다) 컴파일 에러가 발생한다. 이런 경우 제네릭에 타입을 정의해서 사용한다.
// 방법1
function print<T>(text: T[]): T[] {
console.log(text.length);
return text;
}
// 방법2
function print<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
배열 타입은 .length를 사용할 수 있다.
print 제네릭 함수는 배열 타입의 제네릭 T[]
를 매개변수로 받기 때문에 .length를 사용할 수 있다.
type strNum = string | number;
function printStrNum<T extends strNum>(text: T): T {
return text;
}
printStrNum('test')); // test
printStrNum(1234)); // 1234
printStrNum([1, 2, 3]); // Argument of type 'number[]' is not assignable to parameter of type 'strNum'.
printStrNum(false); // Argument of type 'boolean' is not assignable to parameter of type 'strNum'.
printStrNum 함수는 제네릭 타입 T
를 extends 키워드를 통해 타입 별칭으로 선언한 strNum(유니온 타입)으로 제한한다.
interface Length {
length: number;
}
function print<T extends Length>(text: T): T {
console.log(text.length);
return text;
}
print(1234); // Argument of type 'number' is not assignable to parameter of type 'Length'.
print(true); // Argument of type 'boolean' is not assignable to parameter of type 'Length'.
print('test'); // 4
print([1, 2, 3, 4, 5]); // 5
extends 키워드를 사용하면 제네릭 T
를 .length를 사용할 수 있는 타입으로 제한(타입의 종류를 제한)할 수 있다.
제약 조건을 명시하는 인터페이스를 통해 length 프로퍼티를 선언하고 extends 키워드를 사용하여 .length를 사용할 수 있는 타입만 매개변수로 받을 수 있다.
-> 클래스나 인터페이스에서 extends 키워드는 확장을 뜻하지만 제네릭에서 extends 키워드는 제한을 뜻한다.
interface Person {
name: string;
age: number;
}
function getProp<T extends keyof Person>(text: T): T {
return text;
}
getProp('name');
getProp('id'); // Argument of type '"id"' is not assignable to parameter of type 'keyof Person'.
keyof 키워드를 사용해 객체(인터페이스)에 존재하지 않는 프로퍼티에 접근할 수 없도록 제한할 수 있으며 프로퍼티를 잘못 전달하는 실수를 방지한다.
interface Person {
name: string;
age: number;
gender: string;
}
// 제네릭은 함수에서 여러개 지정해서 사용할 수 있다.
function getProp<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let user: Person = {
name: 'lee',
age: 25,
gender: 'female'
};
getProp(user, 'gender');
getProp(user, 'id'); // Argument of type '"id"' is not assignable to parameter of type '"gender" | "name" | "age"'.
getProp 함수의 매개변수인 제네릭 T
타입에는 객체가 들어오는데 keyof 키워드를 통해 객체의 key값(프로퍼티)만 들어올 수 있도록 extends 키워드를 사용해 제네릭 K
에 제한을 건다.
-> 제네릭 K
는 제네릭 T
타입의 프로퍼티를 반드시 포함해야한다.