TypeScript - 제네릭: 초보자 가이드

안녕하세요, 미래의 코딩 슈퍼스타! 오늘 우리는 TypeScript 제네릭의 세계에 흥미로운 여정을 떠납니다. 프로그래밍에 새로운 사람이라고 걱정하지 마세요 - 저는 당신의 친절한 안내자가 되겠습니다. 우리는 단계별로 이를 탐구할 것입니다. 이 튜토리얼이 끝나면, 당신은 제네릭을 마스터하듯 사용할 수 있을 것입니다!

TypeScript - Generics

제네릭이란 무엇이고 왜 중요한가요?

구체적인 내용에 들어가기 전에 간단한 비유로 시작해보겠습니다. 마법의 상자가 있다고 상상해보세요. 때로는 책을 넣고, 때로는 장난감이나 샌드위치를 넣습니다. TypeScript에서 제네릭은 바로 이와 같습니다 - 다양한 타입과 작동할 수 있는 유연하고 재사용 가능한 코드를 만들 수 있습니다.

문제 사례

제네릭이 구원의 손을 내미는 몇 가지 시나리오를 살펴보겠습니다:

  1. 어떤 타입의 배열(숫자, 문자열, 객체)을 뒤집는 함수를 만들고 싶을 때.
  2. 어떤 타입의 데이터를 저장하고检索할 수 있는 클래스를 필요할 때.
  3. 다양한 데이터 타입과 작동할 수 있는 유틸리티 함수를 만들고 싶을 때.

제네릭이 없다면, 각 데이터 타입에 대해 별도의 함수나 클래스를 작성해야 합니다. 이는 많은 반복을 의미하며, 모든 좋은 프로그래머는 반복이 깨끗한 코드의 적임을 안다는 것을 알고 있습니다!

TypeScript 제네릭을 구원하다!

이제 손을 dirt고 제네릭이 어떻게 작동하는지 직접 확인해보겠습니다.

기본 제네릭 함수

다음은 어떤 타입과도 작동할 수 있는 간단한 제네릭 함수입니다:

function identity<T>(arg: T): T {
return arg;
}

이를 분해해보면:

  • <T>는 우리의 타입 매개변수입니다. 타입의 플레이스홀더와 같습니다.
  • (arg: T)는 우리의 함수가 T 타입의 인자를 받는다는 뜻입니다.
  • : T는 우리의 함수가 T 타입의 값을 반환한다는 뜻입니다.

이 함수를 다음과 같이 사용할 수 있습니다:

let output1 = identity<string>("Hello, Generics!");
let output2 = identity<number>(42);

console.log(output1); // "Hello, Generics!"
console.log(output2); // 42

쿨하지 않나요? 같은 함수가 다양한 타입과 작동합니다!

제네릭 인터페이스

제네릭을 인터페이스와도 사용할 수 있습니다. 다음은 예제입니다:

interface GenericBox<T> {
contents: T;
}

let stringBox: GenericBox<string> = { contents: "A secret message" };
let numberBox: GenericBox<number> = { contents: 123 };

console.log(stringBox.contents); // "A secret message"
console.log(numberBox.contents); // 123

우리의 GenericBox는 어떤 타입의 콘텐츠를 저장할 수 있습니다. 마법의 상자에 대해 이야기한 것과 같습니다!

제네릭 클래스

간단한 데이터 스토어로 사용할 수 있는 제네릭 클래스를 만들어보겠습니다:

class DataStore<T> {
private data: T[] = [];

addItem(item: T): void {
this.data.push(item);
}

getItems(): T[] {
return this.data;
}
}

let stringStore = new DataStore<string>();
stringStore.addItem("Hello");
stringStore.addItem("World");
console.log(stringStore.getItems()); // ["Hello", "World"]

let numberStore = new DataStore<number>();
numberStore.addItem(1);
numberStore.addItem(2);
console.log(numberStore.getItems()); // [1, 2]

DataStore 클래스는 어떤 타입의 데이터를 저장하고检索할 수 있습니다. 매우 유용하죠?

제네릭 제약

때로는 제네릭을 사용할 수 있는 타입을 제한하고 싶을 수 있습니다. 이를 제약으로 할 수 있습니다:

interface Lengthy {
length: number;
}

function logLength<T extends Lengthy>(arg: T): void {
console.log(arg.length);
}

logLength("Hello"); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // 오류: Number는 length 프로퍼티를 가지지 않습니다.

여기서 우리의 logLength 함수는 length 프로퍼티를 가진 타입만 작동할 수 있습니다.

제네릭의 이점

이제 제네릭을 실제로 사용해보았으니, 그들의 이점을 요약해보겠습니다:

  1. 코드 재사용성: 한 번 작성하고 여러 타입으로 사용할 수 있습니다.
  2. 타입 안전성: 컴파일 시 타입 관련 오류를 찾을 수 있습니다.
  3. 유연성: 다양한 데이터 타입과 작동할 수 있는 컴포넌트를 만들 수 있습니다.
  4. 명확성: 입력과 출력 간의 관계를 명확하게 만듭니다.

제네릭 메서드 표

다음은 흔히 마주치는 몇 가지 제네릭 메서드 표입니다:

메서드 설명 예제
Array.map<U>() 배열 요소 변환 [1, 2, 3].map<string>(n => n.toString())
Promise.all<T>() 모든 프롬يس가 해결될 때까지 기다림 Promise.all<number>([Promise.resolve(1), Promise.resolve(2)])
Object.keys<T>() 객체 키를 배열로 가져옴 Object.keys<{name: string}>({name: "Alice"})
JSON.parse<T>() JSON 문자열을 객체로 파싱 JSON.parse<{age: number}>('{"age": 30}')

결론

축하합니다! TypeScript 제네릭의 fascinaning 세계로 첫 걸음을 뗐습니다. 강력한 도구인 제네릭은처음에는 조금 어렵게 느껴질 수 있지만, 연습을 통해 두 번째 nature가 될 것입니다.

프로그래밍 여정을 계속하면서, 제네릭은 TypeScript 도구箱에서 다용도, 강력하고 매우 유용한 도구로 자리 잡게 될 것입니다. 그러므로 어린 Padawan, 코드를 작성하고, 제네릭과 함께 여행을 계속하세요!

Happy coding, and until next time, keep exploring and learning!

Credits: Image by storyset