[TypeScript] 제네릭
[TypeScript] 제네릭
제네릭
제네릭이란?!
- 타입을 변수처럼 사용하는 문법
- 다양한 타입을 받아야할 때, 타입을 미리 정하지 않고 사용할 때 정하도록 함.
<>기호를 사용하며, 안에는 type의 의미인 T를 타입 변수로 사용.- 사용할 때,
<>기호와 타입을 명시하여 작성한다. 사용 예시
1 2 3 4
function getFirstElement<T>(element: T[]): T { return element[0]; } const r1 = getFirstElement<number>([1, 2, 3]);
그런데 사용할 때, 명시하지 않아도 정상 동작함.
1 2 3 4
function getFirstElement<T>(element: T[]): T { return element[0]; } const r1 = getFirstElement([1, 2, 3]); // 명시 X
반환형에도 제네릭을 나타내지 않아도, 타입 추론에 의해 정상 반환됨.
1 2 3 4
function getFirstElement<T>(element: T[]) { // 반환형 X return element[0]; } const r1 = getFirstElement([1, 2, 3]);
제네릭 - 함수 표현식(화살표 함수)
함수 표현식에서는 다음과 같이 나타낼 수 있다.
1 2 3
const getFirstElement: <T>(element: T[]) => T = (element) => { return element[0]; };
제네릭 - 타입 별칭
타입 별칭에서는 다음과 같이 표현한다.
→ 제네릭을 타입 별칭명 옆에 제시한다.1 2 3 4 5 6
type MyFunc<T> = (element: T[]) => T; const getFirstElement: MyFunc<unknown> = (element) => { return element[0]; }; const r1 = getFirstElement([1, 2, 3]); const r2 = getFirstElement(["a", "b", "c"]);
다음과 같이 사용하면, 해당 방식은 제네릭 타입별칭이 아닌,
타입을 변수에 저장한 것에 불과한 방식이다.1 2 3 4 5 6
type MyFunc = <T>(element: T[]) => T; const getFirstElement: MyFunc = (element) => { return element[0]; }; const r1 = getFirstElement([1, 2, 3]); const r2 = getFirstElement(["a", "b", "c"]);
제네릭 - 인터페이스
인터페이스에서는 다음과 같이 표현한다.
→ 제네릭을 인터페이스명 옆에 제시한다.1 2 3 4 5 6
interface MyInterface<U> { (element: U[]): U; } const getFirstElement: MyInterface<unknown> = (element) => { return element[0]; };
다음과 같이 사용하면, 해당 방식은 제네릭 인터페이스가 아닌,
타입을 변수에 저장한 것에 불과한 방식이다.1 2 3 4 5 6
interface MyInterface { <T>(element: T[]): T; } const getFirstElement: MyInterface = (element) => { return element[0]; };
제네릭의 타입 변수
- T: Type → 일반적인 타입 변수
U: Another Type → 두 개 사용할 때
1 2 3 4 5 6
function returnTuple<T, U>(a: T, b: U): [T, U] { return [a, b]; } const r3 = returnTuple(1, 2); const r4 = returnTuple([1, 2, 3], { name: "jaegeon" });
- K: Key→ 객체 타입의 키
V: Value→ 객체 타입의 값
1 2 3 4 5
function getValue<K, V>(obj: V, key: K): V[K] { return obj[key]; } const name = getValue({ name: "kim", age: 20 }, "name"); const gender = getValue({ gender: "male" }, "gender");
그러나, 다음 방식은 실행 불가! → 키로 들어오는 것을 제한해야 함
E: Element→ 주로, 배열 요소의 타입
1 2 3 4 5
function firstElement<E>(element: E[]): E { return element[0]; } firstElement([1, 2, 3]); firstElement(["a", "b"]);
R: Return Type → 반환 값의 타입(함수)
⇒ 2개의 제네릭: <T, U>
제네릭 심화
제네릭 타입 제약 조건
extends키워드를 사용하여, 제네릭에 들어오는 타입을 제한할 수 있다.제네릭을 다음과 같이 사용시, 타입을 제한해야 한다.
(예:length를 사용할 수 있는 타입으로 제한되어야 함)1 2 3 4 5 6 7
function getLength<T>(value: T): number { return value.length; } getLength([1, 2]); // 가능 getLength("abc"); // 가능 getLength(10); // 불가능
따라서, 다음과 같이 제약 조건을 걸 수 있다.
(예:length의 속성이 들어간 타입으로만 제한)1 2 3 4 5 6
function getLength<T extends { length: number }>(value: T): number { return value.length; } getLength([1, 2]); getLength("abc"); getLength(10); // 안 됨!
객체의 키, 값에서의 제네릭
앞서 말한
extends키워드를 사용하면,
값에 연결되는 키의 타입으로 제한이 가능하다.1 2 3 4 5 6
function getValue<K extends keyof V, V>(obj: V, key: K): V[K] { return obj[key]; } const name = getValue({ name: "kim", age: 20 }, "name"); const gender = getValue({ gender: "male" }, "gender");
⇒
keyofV: V의 키를 반환
인터페이스에서의 제네릭
- 이전에 제네릭을 인터페이스에서 사용했던 것 처럼, 사용할 때.
코드의 재사용성을 높여준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// 인터페이스 제네릭 interface Car<T> { name: string; options: T; } const car1: Car<{ color: string }> = { name: "BMW", options: { color: "red", }, }; const car2: Car<{ wheels: number }> = { name: "G90", options: { wheels: 4, }, };
타입스크립트 추가 개념
enum 선언 병합
타입 가드의 검사
- 타입 가드는 정적인 상태에서 검사하는 것이 아니다!
- 타입스크립트의 정적 검사 원리에서 벗어나,
타입 가드는 동적인 상태에서 검사하는 것이다. - 타입 단언은 정적 타입일 때 정의 됨.
정적 vs 동적
- 정적 타입과 정적 상태: 컴파일 시점에 데이터 타입이 결정됨 → 타입스크립트
- 동적 타입과 동적 상태: 코드 실행할 때, 데이터 타입이 결정됨 → 자바스크립트
This post is licensed under CC BY 4.0 by the author.