Menu

TypeScript Partial과 Required 사용법: 예제와 DeepPartial

Partial<T>는 T의 모든 속성을 선택적으로 만들며, 업데이트나 패치 객체의 타입으로 딱 맞습니다. 업데이트 함수에서 Partial 쓰기, Partial이 얕은 이유, DeepPartial 만들기, 명시적 undefined 함정, 그리고 반대인 Required<T>를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

Partial<T>는 T의 모든 속성을 선택적으로 만드는 내장 유틸리티 타입입니다. 호출하는 쪽이 바뀐 필드만 보내는 업데이트나 패치에 자연스럽게 맞는 타입입니다.

Partial<User>는 { id?: number; name?: string; email?: string }입니다. 컴파일러는 넘긴 필드는 여전히 검사하므로 { nmae: "x" }나 { name: 42 }는 오류입니다. 이것이 느슨한 object나 any 매개변수보다 Partial이 나은 이유입니다.

Partial의 정의

Partial은 TypeScript 표준 라이브러리에 있는 한 줄짜리 매핑된 타입입니다.

type Partial<T> = {
  [P in keyof T]?: T[P];
};

T의 각 키 P에 대해 같은 타입의 선택적 속성을 선언합니다. keyof T를 매핑하므로 원래 readonly였던 속성은 readonly로 유지됩니다. 평범한 타입이라 런타임 효과는 없습니다. changes로 넘긴 객체는 어느 쪽이든 같은 객체입니다.

Partial에서 읽으면 T | undefined가 된다

Partial<T>의 모든 속성은 없을 수 있으므로, 읽으면 속성 타입에 undefined가 더해집니다. 컴파일러는 없는 경우를 처리하도록 요구합니다.

{ ...defaults, ...opts }는 Partial<Options>를 다시 완전한 Options로 만드는 흔한 방법입니다. 기본값을 먼저 펼치고, 넘어온 값이 그것을 덮어쓰게 합니다.

명시적 undefined 함정

선택적 속성은 없을 수도 있지만 undefined 값으로 존재할 수도 있습니다. 객체 스프레드는 그 undefined를 실제 값 위에 복사하고, 결과 타입에는 그것이 드러나지 않습니다.

빈 필드가 undefined가 되는 폼이나 쿼리 문자열로 패치를 만들 때 이 문제가 생깁니다. exactOptionalPropertyTypes 컴파일러 옵션(strict에 포함되지 않음)을 켜면 name?: string | undefined라고 쓰지 않는 한 name?: string에 대한 { name: undefined }가 컴파일 오류가 되어 원천적으로 막아 줍니다.

Partial은 얕다

Partial은 최상위 속성만 선택적으로 만듭니다. 중첩 객체를 넘긴다면 그 객체는 완전해야 합니다.

index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.

editor 속성은 선택적이지만, 일단 있으면 타입은 그대로 { fontSize: number; tabSize: number }입니다. 설정 객체, API 패치, 테스트 픽스처에서는 모든 단계의 속성이 선택적이기를 바라는 경우가 많습니다. 그러려면 재귀 타입이 필요합니다.

DeepPartial: 재귀적인 Partial

TypeScript에는 깊게 적용되는 내장 버전이 없지만 몇 줄이면 만들 수 있습니다. 배열 요소를 선택적으로 만들면 [undefined]가 허용되므로 함수와 배열은 그대로 둡니다.

타입은 재귀적이지만 병합은 그렇지 않습니다. 객체 스프레드도 얕기 때문에 applySettings는 editor를 직접 병합합니다. 범용 깊은 병합 함수는 lodash(merge) 같은 라이브러리에 있으며, 그 타입 지정은 위의 타입보다 어렵습니다.

Required: Partial의 반대

Required<T>는 모든 속성에서 ?를 제거합니다. -? 수정자로 정의되며, 이 수정자는 각 속성 타입에서 undefined도 제거합니다.

패턴은 Partial과 같고 방향만 반대입니다. API 사용자는 느슨한 설정을 넘기고, 내부 코드는 모든 값이 존재한다고 알려진 Required 버전으로 작업합니다. 이 스프레드에도 위의 업데이트 함수와 같은 구멍이 있습니다. 호출하는 쪽이 port: undefined를 명시적으로 넘기면 기본값이 undefined로 덮어써지고, exactOptionalPropertyTypes가 꺼져 있으면 컴파일러는 이를 받아들입니다. Required도 Partial과 같은 방식으로 얕습니다.

일부 속성만 선택적 또는 필수로 만들기

Partial과 Required는 모든 속성에 적용됩니다. 몇 개만 바꾸려면 Pick과 Omit으로 타입을 나눈 뒤 다시 합칩니다.

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

interface Post {
  id: number;
  title: string;
  body?: string;
}

type NewPost = PartialBy<Post, "id">;      // id optional, title required, body optional
type Published = RequiredBy<Post, "body">; // body now required
타입효과깊게 적용?
Partial<T>모든 속성을 선택적으로아니요
Required<T>모든 속성을 필수로, undefined 제거아니요
DeepPartial<T> (직접 작성)모든 단계에서 선택적으로예
PartialBy<T, K> (직접 작성)키 K만 선택적으로아니요
Readonly<T>모든 속성을 readonly로아니요

자주 묻는 질문

TypeScript에서 Partial은 무엇을 하나요?

Partial<T>는 T의 모든 속성을 선택적으로 표시한 타입을 만듭니다. interface User { name: string; email: string }에 대해 Partial<User>는 { name?: string; email?: string }이므로 {}, { name: "Ada" }, 완전한 사용자 모두 유효한 값입니다.

TypeScript의 Partial은 깊게 적용되나요?

아니요. Partial은 최상위 속성에만 영향을 줍니다. Partial<T> 안의 중첩 객체는 여전히 완전해야 합니다. 재귀적으로 적용하려면 객체 타입 속성에 자기 자신을 적용하는 DeepPartial<T> 타입을 직접 작성하세요.

TypeScript에서 Partial의 반대는 무엇인가요?

Required<T>입니다. 모든 속성에서 ?를 제거하고 타입에서 undefined도 제거하므로 Required<{ port?: number }>는 { port: number }입니다. -? 수정자를 쓴 매핑된 타입으로 정의되어 있습니다.

일부 속성만 선택적으로 만들려면 어떻게 하나요?

Omit, Pick, Partial을 조합합니다: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email">은 email만 선택적으로 바꾸고 나머지 속성은 그대로 둡니다.

Partial 업데이트를 병합한 뒤 속성이 undefined가 되는 이유는 무엇인가요?

Partial<T>는 속성이 undefined 값으로 존재하는 것을 허용하고, 객체 스프레드는 그 값을 복사합니다. TypeScript가 결과를 User로 타입 지정하더라도 { ...user, ...{ name: undefined } }의 name은 undefined입니다. 병합 전에 undefined 값을 걸러내거나, exactOptionalPropertyTypes를 켜서 명시적 undefined를 거부하세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기