TypeScript - Generic

이소라·2022년 6월 11일
0

TypeScript

목록 보기
5/28

Generic

Generic function

function identity<Type>(arg: Type): Type {
	return arg;
}
  • 이렇게 Type varibable을 사용하는 함수를 generic이라고 함
  • Type은 함수 사용자가 제공하는 타입을 받음
  • Type argument에 모든 타입이 들어갈 수 있음

Generic function call

let output = identity<string>("myString");
  • type argument를 명시하는 방법
  • type을 명시할 때 <>로 감싸서 표현함
let output = identity("myString");
  • type argument inference를 사용하는 방법
  • 전달된 argument의 타입으로 Type 값이 자동적으로 설정됨

work with Generic Type variable

  • Type varible에는 모든 타입이 들어갈 수 있으므로, generic 함수에서 특정 타입의 메소드를 사용할 경우 에러가 발생함
function loggingIdentity<Type>(arg: Type): Type {
  console.log(arg.length); // Error
  return arg;
}
  • length 메소드를 사용 가능하게 하려면, Type variable을 Type[]로 선언해야함
function loggingIdentity<Type>(arg: Type[]): Type[] {
  console.log(arg.length);
  return arg;
}
  • Type variable에 Array<Type>으로 선언해도 됨
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
  console.log(arg.length);
  return arg;
}

Generic Constraint

  • extends 키워드로 Generic Type을 제약함
  • extends 키워드 뒤 타입, 유니언 타입, 커스텀 타입 등 여러 타입이 들어갈 수 있음
function merge<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

const mergeObj = merge({name: 'Max', hobbies: ['Sport']}, {age: 30});
console.log(mergeObj);
interface Lengthwise {
  length: number;
}
 
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
  console.log(arg.length);
  return arg;
}

Generic Constraints with Type Parameters

  • keyof 키워드로 Gereric Constraints를 제약함
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key];
}
 
let x = { a: 1, b: 2, c: 3, d: 4 };
 
getProperty(x, "a"); // OK
getProperty(x, "m"); // Error

Generic Classes

  • Generic class는 Type Variable에 따라 다양한 타입의 인스턴스를 생성할 수 있음
class DataStorage<T> {
  private data : T[] = [];
  
  addItem(item: T) {
  this.data.push(item)
  }
  
  removeItem(item: T) {
  this.data.splice(this.data.indexOf(item), 1)
  }
  
  getItems() {
    return [...this.data];
  }
}

const textStorage = new DataStorage<string>();
textStorage.addItem('Max');
textStorage.addItem('Manu');
textStorage.removeItem('Manu');

const numberStorage = new DataStorage<number>();
  • Generic class의 Type Variable로 Primitive Type을 사용하는 것이 좋음
  • Type Variable로 Reference Type을 사용할 경우, 참조할 때 어려움을 겪을 수 있음
class DataStorage<T> {
  private data : T[] = [];
  
  addItem(item: T) {
  	this.data.push(item)
  }
  
  removeItem(item: T) {
  	if (this.data.indexOf(item) === -1) {
      return;
    }
  	this.data.splice(this.data.indexOf(item), 1)
  }
  
  getItems() {
    return [...this.data];
  }
}

const objStorage = new DataStorage<object>();
objStorage.addItem({name: 'Max'});
objStorage.addItem({name: 'Manu'});
objStorage.removeItem({name: 'Max'}); // 새로운 객체 {name: 'Max'}에 대해removeItem 메소드를 호출하므로 기존 객체에는 영향 받지 않음
console.log(objStorage.getItems()); // 예상치 못한 값 {name: 'Max'}가 나옴
const objStorage = new DataStorage<object>();
const maxObj = {name: 'Max'};
objStorage.addItem(maxObj);
objStorage.addItem({name: 'Manu'});
objStorage.removeItem(maxObj); // 같은 객체에 대해 removeItem 메소드를 호출하므로 지워짐
console.log(objStorage.getItems()); // {name: 'Manu'}

Generic Utility Types

Paritial Type

  • Partial<Type>을 사용하면 Type의 모든 속성을 선택적으로 설정 가능한 타입을 만들 수 있음
interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

Readonly Type

  • Readonly<Type>을 사용하면 Type의 모든 속성을 재할당할 수 없음
interface Todo {
  title: string;
}
 
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};
 
todo.title = "Hello"; // Error

Pick Type

  • Pick<Type, property1| property2| ...>을 사용하면 Type의 속성들을 조합하여 사용할 수 있음
interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

Omit Type

  • Omit<Type, Keys>를 사용하면 Type의 모든 속성들 중 Key들을 제거하여 사용할 수 있음
interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};

type TodoInfo = Omit<Todo, "completed" | "createdAt">;
 
const todoInfo: TodoInfo = {
  title: "Pick up kids",
  description: "Kindergarten closes at 5pm",
};

Exclude Type

  • Exclude<UnionType, ExcludedMembers>를 사용하면 Union Type의 멤버들 중에서 ExcludedMembers를 제외하여 사용할 수 있음
type T0 = Exclude<"a" | "b" | "c", "a">;
     // type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
     // type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
     // type T2 = string | number

Union Type vs Generic Type

  • Union Type: 함수를 호출할 때마다 union type 중 하나로 호출할 수 있는 함수가 필요한 경우 유용함
  • Generic Type: 특정 타입을 고정하거나, 전체 클래스 인스턴스에 같은 함수를 사용하거나, 전체 함수에서 같은 타입을 사용할 경우 유용함

Mapped Types with Generics

  • 같은 모양의 다른 타입을 생성해야할 때 사용됨
type BooleanFields<T> = {
  [K in keyof T]: boolean;
}
  • BooleanFields 타입은 T에서 사용가능한 모든 속성 K가 boolean 속성을 갖음
type BooleanFields<T> = {
  [K in keyof T]: boolean;
};

type User = {
  email: string;
  name: string;
}

type UserFetchOptions = BooleanFields<User>;
// { email: boolean; name: boolean; }와 같은 형태임

Conditional Types with Generics

Basic Structure of Conditional Typing

  • conditional type은 조건에 따라 다른 타입을 가짐
type IsStringType<T> = T extends string ? true : false;
  • IsStringType은 T에 string 타입이 들어가면 true 타입이 되고, 아닐 경우 false 타입이 됨
type IsStringType<T> = T extends string ? true : false;

type A = "abc";
type B = {
  name: string;
};

type ResultA = IsStringType<A>; // true
type ResultB = IsStringType<B>; // false

Inferring within Conditional Types

  • infer 키워드를 사용하여 조건에 따라 추론된 타입을 사용함
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;
  • GetReturnType의 T가 함수 형태라면 반환타입을 추론해서 받고, 아니라면 never 타입이 됨
type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;

function someFunction() {
  return true;
}

type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>; // boolean

참고 링크 : TypeScript Handbook, How to Use Generics in TypeScript

0개의 댓글