타입스크립트를 야금야금 조금씩 알아봅시당 2222
지난번에 정리한 글에서 이어져요
Typescript의 유틸리티 타입 (1) - Record, Exclude, Extract, Pick
Partial<T>
Partial은 제네릭 타입 T에 대해서 모든 프로퍼티들을 Optional하게 변경한다.
type Partial<T> = {
[P in keyof T]?: T[P];
};
제네릭 타입의 프로퍼티들에 대해 기존의 타입은 유지하되, 각각의 프로퍼티들을 Optional 타입으로 변경해준다. 필수 타입과 Optional 타입을 구분해서 사용해야 하는 경우 아래와 같이 쓸 수 있다.
type UserInformation = RequiredUserInformation & Partial<OptionalUserInformation>;
interface RequiredUserInformation {
id: string;
uid: string;
name: string;
}
interface OptionalUserInformation {
age: number;
profile: string;
phone: string;
}
Required<T>
Required는 위의 Partial과 반대되는 개념이다. 제네릭 타입 T의 모든 프로퍼티에 대해 Required 속성으로 만들어준다.
type Required<T> = {
[P in keyof T]-?: T[P];
};
마이너스 연산자는 Optional을 제거해준다는 의미의 연산자이다.
Partial 타입과 동일하게 기존의 타입은 유지된 상태에서 Required 타입으로 변경된다.
interface OptionalTodo {
id: string;
text?: string;
isDone?: boolean;
}
type Todo1 = Required<OptionalTodo>;
interface Todo2 {
id: string;
text: string;
isDone: boolean;
} // 타입 Todo1과 타입 Todo2 는 동일한 타입이다.
ReadOnly<T>
T의 모든 프로퍼티를 읽기 전용(readOnly)으로 설정한 타입을 구성한다. 즉 모든 프로퍼티의 값을 변경할 수 없고 참조만 할 수 있도록 만든다.
type ReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
ReadOnly로 생성된 타입에 값을 재할당하려고 하면 에러가 난다.
interface eventState {
title: string;
id: number;
}
const event: ReadOnly<eventState> = {
title: 'new event',
id: 123,
};
event.title = 'Hello'; // 에러 발생!
자바스크립트에서 Object.freeze() 메소드를 통해 객체를 동결하여 사용하는 경우, 이렇게 frozen된 객체에는 새로운 속성을 추가하거나 이미 있는 속성을 삭제 혹은 변경할 수 없다. frozen 객체의 프로퍼티에 재할당 하려고 하는 경우 strict mode에서는 에러가 난다.
ReadOnly 유틸리티 타입을 사용하면 마찬가지로 재할당시에 에러를 뱉는다.
interface subProps {
eventId: string;
}
const freeze = <T extends subProps>(obj: T): Readonly<T> => {
return obj;
};
const originalData = {
eventId: 'event',
};
const newData = freeze(originalData);
newData.eventId = 'hello'; // Cannot assign to 'eventId' because it is a read-only property.
Omit<T, K>
Omit 타입은 두개의 제네릭 타입을 받으며 T에서 모든 프로퍼티를 선택한 다음 K를 제거한 타입을 구성한다.
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
위의 설명이 바로 이해되지 않았는데, 아래처럼 이해하면 좀더 쉽다.
type Event = {
id: number;
title: string;
detail: string;
};
// Event에 속하는 타입들 중 detail를 제외한 타입들을 BasicEventInfo에 넣는다.
type BasicEventInfo = Exclude<keyof Event, "detail">;
// Event에서 BasicEventInfo에 있는 키값에 속하는 프로퍼티들을 리턴한다.
type BasicEventType = Pick<Event, BasicEventInfo>;
//결과 type BasicEventType = { id: number; title: string; };
Pick과는 반대로 T 타입으로부터 K 프로퍼티를 제거한 타입을 구성한다고 볼 수 있다.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
};
보다보니까 Exclude와 다른점이 뭘까라는 생각이 들었는데, Exclude는 두번째 제네릭 타입에 타입이 들어가고 Omit은 키값으로 넣어준다는 게 차이점인 것 같다.
Exclude<T, U> : T에서 U에 할당할 수 있는 타입을 제외한 타입 반환
Omit<T, U> : T의 모든 프로퍼티 중 U가 제거된 프로퍼티들의 타입 반환
NonNullable<T>
주어진 제네릭 타입 T에서 null과 undefined를 제외한 타입을 구성한다.
type NonNullable<T> = T extends null | undefined ? never : T;
null 혹은 undefined 타입이거나 상속한다면 무시하고, 아니라면 타입을 리턴한다.
type NotNullType = NonNullable<string | number | undefined>;
// NotNullType은 string | number
ReturnType<T>
유틸리티 타입 중에 제일 자주 봤던 타입이다.
ReturnType 타입은 주어진 제네릭 타입 T의 return type을 할당한다.
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
음 어려워
T는 (…args: any) => any
를 상속한다. 모든 타입의 파라미터를 인자로 받고 결과값으로 모든 타입의 값을 리턴하기 때문에 사실상 모든 함수를 상속한다.
infer라는 키워드는 타입을 추론할 때 사용하는데, 추론된 타입 값을 R에 할당해준다.
T extends (...args: any) => infer R ? R : any
는 R 타입에 대해서 타입 추론이 가능하다면 R 타입을, 그렇지 않다면 any 타입을 반환한다. 쉽게 말해서 함수의 리턴 타입을 반환한다.
예시를 좀더 보자.
type T0 = ReturnType<() => string>; // string
type T1 = ReturnType<(s: string) => void>; // void
interface Payload {
foo: string;
bar: string;
}
const fooBarCreator = (): Payload => ({
foo: "foo",
bar: "bar"
});
type FooBarCreator = ReturnType<typeof fooBarCreator>;
// type IFooBarCreator = Payload
RTK를 사용할 때도 아래와 같은 코드를 쓴다. useSelector를 사용할 경우 사용하는 곳에서 매번 state의 타입 정의를 해주는 대신 useAppSelector라는 hook을 만들어서 사용하는데, 여기에 주입해주는 state 타입을 가져올 때 아래와 같은 코드를 사용한다.
import { combineReducers } from '@reduxjs/toolkit';
const rootReducer = combineReducers({});
export type RootState = ReturnType<typeof rootReducer>;
rootReducer 함수가 리턴한 결과의 타입을 가져와서 RootState에 할당한다.
참고 :
Typescript의 기본 유틸 타입
(https://blog.martinwork.co.kr/typescript/2019/05/28/typescript-util-types.html)
타입스크립트 핸드북
(https://www.typescriptlang.org/ko/docs/handbook/utility-types.html)
Typescript 유틸리티 클래스 파헤치기
(https://medium.com/harrythegreat/typescript-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-7ae8a786fb20)