제네릭?
- 타입을 마치 클래스나 함수 등에서 파라미터처럼 사용하는 것을 의미한다.
class ArrayOfNumbers {
constructor(public collection: number[]) {}
get(index: number): number {
return this.collection[index];
}
}
class ArrayOfStrings {
constructor(public collection: string[]) {}
get(index: number): string {
return this.collection[index];
}
}
number 타입, string 타입인거를 제외하면 나머지 부분은 전반적으로 동일하다. number 배열, string 배열을 생성자에서 매개변수로 받고 그 중에서 특정 인덱스의 값을 리턴하는 코드다.
타입 빼고 나머지 코드는 동일하다 즉, 중복되는 부분이 굉장히 많은데 이거를 하나의 클래스로 만들 수는 없을까?
이 두 개의 코드에서 중복되는 부분을 하나로 묶어서 하나의 클래스로 만들어 보자
class ArrayOfAnything<T> {
constructor(public collection: T[]) {}
get(index: number): T {
return this.collection[index];
}
}
new ArrayOfAnything<string>(['a', 'b', 'c'])
- 어떤 타입이 들어올지 모르기 때문에 <T> 이렇게 타입이 들어온다고 하고 collection 타입 어노테이션과 get 함수에 각각 T로 만들어주므로 T가 number일 때는 number 배열, number 리턴, string일 때는 string 배열 string 리턴으로 위에서 작성했던 두 가지의 역할을 동일하게 할 수 있다.
⇒ 클래스에서 제네릭을 통해서 중복되는 내용들이 많은 클래스를 하나로 합치는 코드로 만들수 있다.
그럼 함수는?
function printStrings(arr: string[]): void {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
function printNumbers(arr: number[]): void {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
**function printAnything<T>(arr: T[]): void {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}**
printAnything(['a', 'b', 'c']); // <string>을 써주지 않아도 타입 추론이 가능
printAnything([1,2,3]); // number 배열도 받을 수 있다
- 자동적으로 타입을 추론하기 때문에 따로 타입을 적어주지 않아도 문제없이 돌아간다.
하지만 제네릭을 마음껏 사용하면 될까?
- 규칙을 제한할 수 있다
class Car {
print() {
console.log('car');
}
}
class House {
print() {
console.log('house');
}
}
function printHousesOrCars<T>(arr: T[]): void {
for (let i = 0; i < arr.length; i++) {
arr[i].print();
}
}
printHousesOrCars([1,2,3]);
- 이 제네릭이 print를 가질 수 있게 제약조건을 만들어 줘야 한다
- print 메서드가 없으면 에러를 반환한다
class Car {
print() {
console.log('car');
}
}
class House {
print() {
console.log('house');
}
}
interface Printable {
print(): void
}
function printHousesOrCars<T extends Printable>(arr: T[]): void {
for (let i = 0; i < arr.length; i++) {
arr[i].print();
}
}
printHousesOrCars([1,2,3]); // error
printHousesOrCars<House>([new House(), new House()])
타입가드?
타입스크립트로 개발 하다보면 때로는 굉장히 타입이 넓게 정의가 되어서 타입스크립트의 유효성을 올바르게 가져가지 못 하는 경우가 많다. 그래서 이러한 문제들을 해결하기 위해서 사용하는 개념이 타입 가드이다.
- 타입 가드는 조건문에서 객체의 타입을 좁혀 나갈 수 있는 방법이다.
- 타입을 분기처리를 통해서 정확한 타입을 찾고 그 안에서 정확한 로직을 만들어야 나중에 사이드 이펙트나 버그가 줄어들 수 있게 된다.
- 타입 가드에는 굉장히 많은 문법들이 있다.
typeof
우리가 유니언 타입으로 인자를 받는다고 한 번 가정해 보자
function doSomething(x: number | string) {
if (typeof x === 'string') {
console.log(x.substr(1)); // string에는 substr 메서드가 존재하지만, number에는 존재하지 않습니다.
}
}
- 인자를 number와 string 타입으로 받고 있는데 만약에 여기에서 x의 substr 을 구하고 싶을 때 substr는 string 타입에만 가지고 있기 때문에 number에는 없다.
- 타입스크립트에서는 이 x가 number인지 string인지 모르기 때문에 에러를 뱉고 있다.
- 이러한 경우에 타입 가드를 통해서 조건문을 분기처리 할 수 있다.
instanceof
- 타입을 위에처럼 찾는 게 아니라 어떤 클래스를 인스턴스로 만들어 쓰는 경우에 사용
class Foo {
foo = 123;
common = '123';
}
class Bar {
bar = 123;
common = '123';
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo)
}
if (arg instanceof Bar) {
console.log(arg.bar);
}
console.log(arg.common);
console.log(arg.foo); // Error
console.log(arg.bar); // Error
}
- 위에 두 가지 필드를 가지고 있는 클래스로 예를 들어보자.
- arg 인자가 Foo 혹은 Bar 클래스를 가지고 있을 때 둘 다 common 속성을 가지고는 있지만 둘 다 foo나 bar를 가지고 있지 않기 때문에 에러를 뱉는다.
- 이럴 때는 인자가 instanceof Foo 혹은 Bar라면 이라는 타입 가드를 통해서 에러를 발생하지 않게 할 수 있다.
in
- 객체 안에서 어떤 프로퍼티를 가지고 있는지 파악하는 문법이다.
interface A {
x: number;
}
interface B {
y: string;
}
function doStuff(q: A | B) {
if ('x' in q) {
// q: A
}
else {
// q: B
}
}
- 각각의 타입별로 분기 처리할 수 있다.
'Front-End > TypeScript' 카테고리의 다른 글
유틸리티 타입 (3) | 2024.07.24 |
---|---|
타입스크립트 클래스 & 인터페이스 (0) | 2024.07.24 |
타입 어노테이션 & 추론 (2) | 2024.07.24 |
타입스크립트란 무엇인가 (2) | 2024.07.24 |
[Typescript] 타입스크립트란? (1) | 2022.12.14 |