반응형
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

Utility Types 본문

TS/TS(with ZeroCho)

Utility Types

5_hyun 2022. 8. 21. 17:57
반응형

Utility Types

https://www.typescriptlang.org/docs/handbook/utility-types.html

 

Documentation - Utility Types

Types which are globally included in TypeScript

www.typescriptlang.org

utility types는 ts가 미리 만들어 놓고 가져다 쓰면 되는 타입들이다. 객체를 조작할 때 도움이 많이 된다. 직접 만들 수도 있어야 한다.

 

예시

Partial

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

const ori: Profile = {
  name: "ori",
  age: 20,
  married: false,
};

// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: Profile = {
  name: "ori",
  age: 20,
};

newOri는 개인정보 보호를 위해 결혼 여부를 제외하고 싶다. 하지만 이런식으로 코드를 짜면 에러가 난다.

 

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

const ori: Profile = {
  name: "ori",
  age: 20,
  married: false,
};

// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: Partial<Profile> = {
  name: "ori",
  age: 20,
};

이렇게 하면 오류가 발생하지 않는다. 왜냐하면 partial은 Profile의 내용을 다 옵셔널로 만들어 주기 때문이다.

 

=> 하지만 partial이 그렇게 좋은 기능은 아니다. 왜냐하면 빈 객체가 생길수도 있기 때문이다.

 

-partial을 구현하기

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

type P<T> = {
  // Profile의 name, age, married가 올 수 있다. 그리고 이것을 옵셔널로 만들 수 있다.
  [key in keyof T]?: T[key];
}

const ori: Profile = {
  name: "ori",
  age: 20,
  married: false,
};


// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: P<Profile> = {
  name: "ori",
  age: 20,
};

type P<T> 처럼 구현하면 된다. T[key]를 하면 타입도 가져올 수 있다. 이렇게 하나하나를 옵셔널로 만들어주면 된다.

Pick과 Omit

Pick

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: Pick<Profile, "name" | "age"> = {
  name: "ori",
  age: 20,
};

뒤에 써 준 것만 뽑아서 사용할 수 있다.

 

-pick 만들기

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

type P<T, S extends keyof T> = {
  [key in S]: T[key];
};

const ori: Profile = {
  name: "ori",
  age: 20,
  married: false,
};

// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: P<Profile, "name" | "age"> = {
  name: "ori",
  age: 20,
};

type P 처럼 만들어야 하는데 S extends keyof T를 한 이유는 S와 T가 연결되어 있기 때문이다. 만약 저 예시에 Profile에 car 속성이 없는데 car 속성을 pick하면 안되니까 extends가 필요하다. 주의 할 점으로는 제네릭을 쓰면 제네릭의 제한 조건을 걸어두는 것이 제일 먼저이다.

 

Omit

interface Profile {
  name: string;
  age: number;
  married: boolean;
}

// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: Omit<Profile, "married"> = {
  name: "ori",
  age: 20,
};

뒤에 써 준 것만 제외하고 사용할 수 있다.

 

-omit 만들기(밑에 Exclude 참고)

const newOri: Pick<Profile, Exclude<keyof Profile, "married">> = {
  name: "ori",
  age: 20,
};

omit은 Pick와 Exclude를 활용해서 만들 수 있다. 따라서 Exclude로 married를 제외하고 Pick로 선택하면 Omit을 만들 수 있다.

type O<T, S extends keyof any> = Pick<T, Exclude<keyof T, S>>;
// 개인정보 보호를 위해 결혼 여부 제외하고 싶음
const newOri: O<Profile, "married"> = {
  name: "ori",
  age: 20,
};

따라서 Omit은 Pick와 Exclude를 조합해서 만들 수 있다. S에 아무 값이나 오면 안되서 extends를 활용해 다른 어떤 것의 키 값만 오도록 했다.

Exculde와 Extract

Exclude

type Animal = 'Cat'|'Dog'|'Human';
type Mammal = Exclude<Animal, 'Human'>;

이렇게 하면 Mammal에는 Animal에서 Human을 제외한다.

따라서 이렇게 나오게 된다.

 

-Exclude

type Exclude<T, U> = T extends U ? never : T;

Exclude는 제외하는 거라서 T가 U의 부분 집합이면 naver로 없애버리고 아니면 포함한다.

Extract

type Animal = "Cat" | "Dog" | "Human";
type Human = Extract<Animal, "Cat" | "Dog">;

Extract는 뒤에 쓴 키를 추출해낸다. 따라서 Human의 타입은 Cat | Dog가 된다.

 

-Extract

type Extract<T, U> = T extends U ? T : never;

Extract는 포함하는 거니까 T가 U의 부분 집합이면 포함하고 아니면 없앤다.

 

=> Exclude와 Extract는 반대이다.

Required

interface Profile {
  name?: string;
  age?: number;
  married?: boolean;
}

const newOri: Required<Profile> = {
  name: "ori",
  age: 20,
  married: false,
};

Required는 옵셔널로 만들어진 키들에서 옵셔널을 제거하는 것이다.

 

-Required 만들기

interface Profile {
  name?: string;
  age?: number;
  married?: boolean;
}

type R<T> = {
  [key in keyof T]-?: T[key];
};

const newOri: R<Profile> = {
  name: "ori",
  age: 20,
  married: false,
};

-?하면 옵셔널을 마이너스한다는 의미로 옵셔널이 사라진다.

Readonly

interface Profile {
  name?: string;
  age?: number;
  married?: boolean;
}

const newOri: Readonly<Profile> = {
  name: "ori",
  age: 20,
  married: false,
};

newOri.name = "babo";

Readonly 값을 사용하면 객체 값을 수정할 수 없다.

 

-Readonly 만들기

interface Profile {
  name?: string;
  age?: number;
  married?: boolean;
}

type R<T> = {
  readonly [key in keyof T]: T[key];
};

const newOri: R<Profile> = {
  name: "ori",
  age: 20,
  married: false,
};

newOri.name = "babo";

이렇게 가져올 때 readonly를 붙이면 된다.

 

-readonly 제외

interface Profile {
  readonly name?: string;
  readonly age?: number;
  readonly married?: boolean;
}

type R<T> = {
  -readonly [key in keyof T]: T[key];
};

const newOri: R<Profile> = {
  name: "ori",
  age: 20,
  married: false,
};

newOri.name = "babo";

-readonly하면 readonly를 제외하고 가져올 수 있다.

Record

interface Obj {
  [key: string]: number;
}

const a: Obj = { a: 3, b: 5, c: 7 };

원래 객체를 만들기 위해서는 이렇게 해야한다.

 

const a: Record<string, number> = { a: 3, b: 5, c: 7 };

Record를 사용하면 바로 객체를 만들 수 있다.

 

-Record 만들기

type R<T extends keyof any, S> = {
  [key in T]: S;
};
const a: Record<string, number> = { a: 3, b: 5, c: 7 };

객체의 키는 number, string, symbol만 올 수 있어서 이 제한 조건을 주기 위해 extends keyof any가 붙었다.

NonNullable

type A = string | null | undefined | boolean | number;
type B = NonNullable<A>;

이러면 B는 A에서 null과 undefined를 제외하고 가져온다.

 

-NonNullable 만들기

type A = string | null | undefined | boolean | number;
type B = N<A>;

type N<T> = T extends null | undefined ? never : T;

삼항연산자를 이용해서 null이거나 undefined면 없앤다.

Parameters와 ReturnType

Parameters

function zip(
  x: number,
  y: string,
  z: boolean
): { x: number; y: string; z: boolean } {
  return { x, y, z };
}

type Params = Parameters<typeof zip>; // [number, string, boolean]
type a = Params[0]; // number
type b = Params[1]; // string
type c = Params[2]; // boolean

Parameters를 사용하면 배열에 타입을 담아두고 인덱스 번호로 불러올 수 있다.

 

-Parameters 만들기

function zip(
  x: number,
  y: string,
  z: boolean
): { x: number; y: string; z: boolean } {
  return { x, y, z };
}

type P<T extends (...args: any) => any> = T extends (...args: infer A) => any ? A : never;

type Params = P<typeof zip>; // [number, string, boolean]
type a = Params[0]; // number
type b = Params[1]; // string
type c = Params[2]; // boolean

(...args: any) => any는 함수 제한 두는 법이다. 따라서 T는 무조건 함수여야 한다.

infer는 Ts가 알아서 매개변수를 추론한다. 따라서 추론한 값이 있으면 사용하라는 것이고 없으면 사용하지 말라는 의미이다.

ReturnType

function zip(
  x: number,
  y: string,
  z: boolean
): { x: number; y: string; z: boolean } {
  return { x, y, z };
}

type Ret = ReturnType<typeof zip>;

ReturnType은 리턴 타입을 가져올 수 있다.

 

-ReturnType 만들기

function zip(
  x: number,
  y: string,
  z: boolean
): { x: number; y: string; z: boolean } {
  return { x, y, z };
}

type R<T extends (...args: any) => any> = T extends (...args: any) => infer A
  ? A
  : never;
type Ret = R<typeof zip>;

infer를 뒤로 빼면 리턴 타입을 가져올 수 있다.

ConstructorParameters와 InstanceType

ConstructorParameters

class A {
  a: string;
  b: number;
  c: boolean;
  constructor(a: string, b: number, c: boolean) {
    this.a = a;
    this.b = b;
    this.c = c;
  }
}
const c = new A("123", 456, true);
type C = ConstructorParameters<typeof A>;

클래스에서 생성자의 타입을 얻어오고 싶으면 사용하면 된다.

InstanceType

class A {
  a: string;
  b: number;
  c: boolean;
  constructor(a: string, b: number, c: boolean) {
    this.a = a;
    this.b = b;
    this.c = c;
  }
}
const c = new A("123", 456, true);
type I = InstanceType<typeof A>;

인스턴스의 타입을 얻어오고 싶으면 사용하면 된다. 

const a: A = new A("123", 456, true);

클래스는 타입으로 사용할 수 있어서 이렇게 할 수 있다.

반응형

'TS > TS(with ZeroCho)' 카테고리의 다른 글

axios 분석  (1) 2022.09.05
npm 사이트에서 JS, TS에 따른 설치법  (0) 2022.08.25
TS와 건망증  (0) 2022.08.20
리턴값, 매개변수의 대입 범위  (0) 2022.08.20
타입을 만드는 법  (0) 2022.08.20
Comments