Завдання

Реалізуйте тип, що додає нове поле до інтерфейсу. Тип приймає три аргументи. Повертає об’єкт з доданим полем.

Наприклад:

type Test = { id: "1" };
type Result = AppendToObject<Test, "value", 4>; // expected to be { id: '1', value: 4 }

Розв’язок

Коли ми хочемо змінити об’єкти/інтерфейси в TypeScript, зазвичай для цього бувають корисні типи перетину (intersection types). Ця задача не виняток. Я спробував написати тип, що приймає T та об’єкт з новою властивістю:

type AppendToObject<T, U, V> = T & { [P in U]: V };

На жаль, це рішення не задовольняє тести. Вони очікують плоский тип, а не перетин. Тому нам необхідно повернути об’єкт, в якому є всі властивості разом з нашою новою. Почнемо з того, що повернемо властивості T:

type AppendToObject<T, U, V> = { [P in keyof T]: T[P] };

Тепер нам потрібно додати до властивостей T нову властивість U. Для цього ми можемо передати об’єднання типів оператору in:

type AppendToObject<T, U, V> = { [P in keyof T | U]: T[P] };

Таким чином ми отримаємо всі властивості з T разом з властивостями U, саме те, що нам потрібно. Виправимо незначні помилки обмеживши U:

type AppendToObject<T, U extends string, V> = { [P in keyof T | U]: T[P] };

Останнє, що TypeScript не може гарантувати - це те, що P може не бути в T, бо P - об’єднання T і U. Нам необхідно додати перевірку: якщо P з T - отримаємо T[P], в іншому випадку V:

type AppendToObject<T, U extends string, V> = {
  [P in keyof T | U]: P extends keyof T ? T[P] : V;
};

Посилання