이름<타입매개변수>
someFunction<T>
, SomeInterface<T>
함수를 제네릭으로 만들면 해당 타입 매개변수를 함수 본문 내부의 매개변수 타입 에너테이션, 반환 타입 애너테이션, 타입 에너테이션에서 사용할 수 있다.
function identity<T>(input: T) {
return input;
}
// 화살표 함수
const identity = <T>(input: T) => input;
❗️제네릭 화살표 함수 구문은 .tsx 파일에서 JSX 구문과 충돌할 수 있으므로 설정을 바꿔야 한다.
function logWrapper<Input>(callback: (input: Input) => void) {
return (input: Input) => {
console.log("input", input);
callback(input);
};
}
// 타입: (input: string) => void
logWrapper((input: string) => {
console.log(input.length);
});
// 타입: (input: unknown) => void
logWrapper((input) => {
console.log(input.length); // Error: 'input' is of type 'unknown'.
});
// Input 제네릭을 위한 명시적 string 작성
// 타입스크립트는 제네릭 타입 Input의 콜백 input 매개변수가 string 타입으로 해석된다고 유추함
logWrapper<string>((input) => {
console.log(input.length);
});
타입 매개변수가 여러개인 경우 쉼표로 구분해 함수를 정의한다.
// makeTuple은 두 개의 타입 매개변수를 선언하고 입력된 값을 읽기 전용 튜플로 반환한다.
function makeTuple<First, Second>(first: First, second: Second) {
return [first, second] as const; // 읽기 전용 튜플로 반환
}
// 타입: readonly [boolean, string]
let tuple = makeTuple(true, "abc");
함수가 여러 개의 타입 매개변수를 선언하면 함수를 호출 시 명시적으로 제네릭 타입을 모두 선언하거나 선언하지 않아야 한다. 타입스크립트는 제네릭 호출 중 일부 타입만 유추하지 못한다.
function makeTuple<First, Second>(first: First, second: Second) {
return [first, second] as const;
}
// OK: 타입 인수가 둘 다 제공되지 않음
makeTuple(true, "abc");
// OK: 두 개의 타입 인수가 제공됨
makeTuple<boolean, string>(true, "abc");
makeTuple<true, "abc">(true, "abc");
// Error: Expected 2 type arguments, but got 1.
makeTuple<string>(true, "abc");
인터페이스도 제네릭으로 선언 할 수 있다.
interface Box<T> {
inside: T;
}
let stringBox: Box<string> = {
inside: "abc"
}
let numberBox: Box<number> = {
inside: 123
}
타입스크립트는 제네릭 타입을 취하는 것으로 선언된 위치에 제공된 값의 타입에서 타입 인수를 유추한다.
interface LinkedNode<Value> {
next?: LinkedNode<Value>;
value: Value;
}
function getLast<Value>(node: LinkedNode<Value>): Value {
return node.next ? getLast(node.next) : node.value;
}
// 유추된 Value 타입 인수: Date
let lastDate = getLast({
value: new Date("09-13-1993"),
});
// 유추된 Value 타입 인수: string
let lastFruit = getLast({
next: { value: "banana" },
value: "apple",
});
// 유추된 Value 타입 인수: number
let lastMismatch = getLast({
next: { value: 123 },
value: false, // Error: Type 'boolean' is not assignable to type 'number'.
});
인터페이스가 타입 매개변수를 선언하는 경우, 해당 인터페이스를 참조하는 모든 타입 애너테이션은 이에 상응하는 타입 인수를 제공해야 한다.
interface Add<T> {
contents: T;
}
let missing: Add = { // Error: Generic type 'Add<T>' requires 1 type argument(s).
// code...
contents: "??"
}
클래스의 각 인스턴스는 타입 매개변수로 각자 다른 타입 인수 집합을 가진다.
class Secret<Key, Value> {
key: Key;
value: Value;
constructor(key: Key, value: Value) {
this.key = key;
this.value = value;
}
getValue(key: Key): Value | undefined {
return this.key === key ? this.value : undefined;
}
}
const storage = new Secret(12345, "luggage"); // 타입: Secret<number, string>
storage.getValue(1987); // 타입: string | undefined
class CurriedCallback<Input> {
#callback: (input: Input) => void;
constructor(callback: (input: Input) => void) {
this.#callback = (input: Input) => {
console.log("Input:", input);
callback(input);
}
};
call(input: Input) {
this.#callback(input);
}
}
// CurriedCallback의 Input 타입 인수를 string으로 명시적으로 제공
// 타입: CurriedCallback<string>
new CurriedCallback((input: string) => {
console.log(input.length);
});
// 타입: CurriedCallback<unknown>
new CurriedCallback((input) => {
console.log(input.length);
// Error: Property 'length' does not exist on type 'unknown'.
});
클래스를 확장하는 경우, extends
키워드 다음에 오는 기본 클래스에 타입 인수를 제공한다.
class Quote<T> {
lines: T;
constructor(lines: T) {
this.lines = lines;
}
}
// SpokenQuote 클래스는 기본 클래스 Quote<T>에 대한 T 타입 인수로 string을 제공
class SpokenQuote extends Quote<string[]> {
speak() {
console.log(this.lines.join("\n"));
}
}
new Quote("The only real failure is the failure to try.").lines; // 타입: string
new Quote([4, 8, 15, 16, 23, 42]).lines; // 타입: number[]
new SpokenQuote([ "Greed is so destructive.", "It destroys everything", ]).lines; // 타입: string[]
new SpokenQuote([4, 8, 15, 16, 23, 42]);
// Error: Argument of type 'number' is not assignable to parameter of type 'string'.
class BothLogger<OnInstance> {
instanceLog(value: OnInstance) {
console.log(value);
return value;
}
static staticLog<OnStatic>(value: OnStatic) {
let fromInstance: OnInstance;
// Error: Static members cannot reference class type arguments.
console.log(value);
return value;
}
}
// 데이터의 성공 결과, 실패 결과를 나타내는 제네릭 '결과' 타입을 만들기 위해 타입 인수를 추가했다.
// succeeded 판별자는 성공/실패 여부에 대한 결과를 좁히는 데 사용한다.
type Result<Data> = FailureResult | SuccessfulResult<Data>;
interface FailureResult {
error: Error;
succeeded: false;
}
interface SuccessfulResult<Data> {
data: Data;
succeeded: true;
}
function handleResult(result: Result<string>) {
if (result.succeeded) {
// result: SuccessfulResult<string>의 타입
console.log('We did it! ${result.data}');
} else {
// result: FailureResult의 타입
console.error('Awww... ${result.error}');
}
result.data;
// Error: Property 'data' does not exist on type 'Result<string>'.
// Property 'data' does not exist on type 'FailureResult'.
}
타입 매개변수 뒤에 = 기본타입
을 작성하면 제네릭의 기본값을 설정할 수 있다.
interface Quote<T = string> {
value: T;
}
타입 매개변수는 동일한 선언 안의 앞선 타입 매개변수를 기본값으로 가질 수 있다.
interface KeyValue<Key, Value = Key> {
key: Key;
value: Value;
}
매개변수 이름 뒤에 extends
키워드와 함께 제한할 타입을 작성하면 타입 매개변수를 제한할 수 있다.
interface WithLength {
length: number;
}
function logWithLength<T extends WithLength>(input: T) {
console.log(`Length: ${input.length}`);
return input;
}
logWithLength("Hello");
logWithLength([1, 2, 3]);
logWithLength({length: 123});
logWithLength(new Date()); // Error
extends
와 keyof
를 함께 사용하면 타입 매개변수를 이전 타입 매개변수의 키로 제한할 수 있다. (제네릭 타입의 키를 지정하는 유일한 방법)
function get<T, Key extends keyof T>(container: T, key: Key) {
return container[key];
}
const roles = {
favorite: "Fargo",
others: ["Almost Famous", "Burn After Reading", "Nomadland"],
};
const favorite = get(roles, "favorite"); // 타입: string
const others = get(roles, "others"); // 타입: string[]
const missing = get(roles, "extras");
// Error: Argument of type '"extras"' is not assignable
// to parameter of type '"favorite" | "others"'.
// 타입: Promise<unknown>
const resolvesUnknown = new Promise((resolve) => {
setTimeout(() => resolve("Done!"), 1000);
});
// 타입: Promise<string>
const resolvesString = new Promise<string>((resolve) => {
setTimeout(() => resolve("Done!"), 1000);
});
async 함수의 반환 타입은 Promise 타입이다.
// OK: Promise<string>
async function givesPromiseForString1(): Promise<string> {
return "Done!";
}
// OK: Promise<string>
async function givesPromiseForString2() {
return "Done!";
}
// Error: The return type of an async function
// or method must be the global Promise<T> type.
async function givesString(): string {
return "Done!";
}
타입 매개변수에 대한 표준 명명 규칙
// L과 V가 어떤 의미인지 알 수 없다.
function lableBox<L, V>(l: L, v: V) { }
// 좀 더 명확하다.
function lableBox<Label, Value>(label: Label, value: Value) { }