Menu

Partial и Required в TypeScript: примеры и DeepPartial

Partial<T> делает каждое свойство T необязательным, и это ровно тип объекта обновления или патча. Partial в функциях обновления, почему он поверхностный, как написать DeepPartial, ловушка явного undefined и противоположный ему Required<T>.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Partial<T> это встроенный служебный тип, который делает каждое свойство T необязательным. Это естественный тип для обновления или патча: вызывающий код передаёт только изменившиеся поля.

Partial<User> это { id?: number; name?: string; email?: string }. Компилятор по-прежнему проверяет поля, которые вы передаёте: { nmae: "x" } или { name: 42 } это ошибка, и именно поэтому Partial лучше расплывчатого параметра object или any.

Как определён Partial

Partial это однострочный сопоставленный тип в стандартной библиотеке TypeScript:

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

Для каждого ключа P из T он объявляет необязательное свойство того же типа. Поскольку он перебирает keyof T, он сохраняет readonly у свойств, где он был. Это обычный тип, поэтому он не влияет на выполнение: объект, переданный как changes, в любом случае один и тот же.

Чтение из Partial даёт T | undefined

Любое свойство Partial<T> может отсутствовать, поэтому его чтение даёт тип свойства плюс undefined. Компилятор заставляет обработать случай отсутствия:

{ ...defaults, ...opts } это обычный способ превратить Partial<Options> обратно в полный Options: сначала развернуть значения по умолчанию, а затем позволить переданным значениям их переопределить.

Ловушка явного undefined

Необязательное свойство может отсутствовать, но может и присутствовать со значением undefined. Развёртывание объекта копирует это undefined поверх настоящего значения, а тип результата этого не показывает:

Это кусается, когда патч собирается из формы или строки запроса, где пустые поля становятся undefined. Параметр компилятора exactOptionalPropertyTypes (не входит в strict) делает { name: undefined } ошибкой компиляции для name?: string, если не написать name?: string | 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нет

Часто задаваемые вопросы

Что делает Partial в TypeScript?

Partial<T> создаёт тип, в котором все свойства T помечены как необязательные. Для interface User { name: string; email: string } Partial<User> это { name?: string; email?: string }, поэтому {}, { name: "Ada" } и полный пользователь это допустимые значения.

Partial в TypeScript глубокий?

Нет, Partial влияет только на свойства верхнего уровня. Вложенный объект внутри Partial<T> по-прежнему должен быть полным. Для рекурсивной версии напишите тип DeepPartial<T>, который применяет себя к свойствам с объектным типом.

Что противоположно Partial в TypeScript?

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, которое становится необязательным.

Почему свойство стало undefined после слияния с Partial-обновлением?

Partial<T> разрешает свойству присутствовать со значением undefined, а развёртывание объекта его копирует: { ...user, ...{ name: undefined } } содержит name: undefined, хотя TypeScript типизирует результат как User. Отфильтруйте значения undefined перед слиянием или включите exactOptionalPropertyTypes, чтобы явное undefined отвергалось.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ