Ответ 1
Typescript теперь поддерживает частичные типы.
Правильный способ создания частичного типа:
type PartialUser = Partial<IUser>;
Допустим, у меня есть интерфейс:
interface IUser {
email: string;
id: number;
phone: string;
};
Затем у меня есть функция, которая ожидает подмножество (или полное совпадение) этого типа. Может быть, он пропустит весь объект, а просто передаст {email: "[email protected]"}
. Я хочу, чтобы проверка типов позволила им обоим.
Пример:
function updateUser(user: IUser) {
// Update a "subset" of user attributes:
$http.put("/users/update", user);
}
Поддерживает ли Typescript такого рода поведение? Я мог бы найти это очень полезным, особенно с такими парадигмами, как Redux.
Чтобы уточнить, цель:
if
, которая лишается преимуществ проверки типов времени компиляции.ОБНОВЛЕНИЕ: Typescript объявил о поддержке отображаемых типов, которые должны решить эту проблему после публикации.
Typescript теперь поддерживает частичные типы.
Правильный способ создания частичного типа:
type PartialUser = Partial<IUser>;
Вы можете объявить некоторые или все поля в качестве необязательных полей.
interface IUser {
email: string; // not optional
id?: number; // optional
phone?: string; // optional
};
правильное решение с отображенными типами:
updateUser<K extends keyof IUser>(userData: {[P in K]: IUser[P]}) {
...
}
Вы можете разделить его на разные интерфейсы:
interface IUser {
id: number;
};
interface IUserEmail extends IUser {
email: string;
}
interface IUserPhone extends IUser {
phone: string;
}
Попросите ваш метод получить базовый интерфейс IUser
, а затем проверьте нужные поля:
function doit(user: IUser) {
if (user.email) {
} else if (user.phone) {
}
}
Если я правильно понял этот вопрос, вам нужно что-то вроде Flow $Shape
Итак, в одном месте у вас может быть что-то, что требует типа
interface IUser {
email: string;
id: number;
phone: string;
};
Затем в другом месте вам нужен тип с тем же типом, что и IUser, только со всеми полями, которые теперь являются необязательными.
interface IUserOptional {
email?: string;
id?: number;
phone?: string;
};
Вам нужен способ автоматического генерации IUserOptional
на основе IUser
без необходимости повторного вывода типов.
Теперь я не думаю, что это возможно в Typescript. Вещи могут измениться в 2.0, но я не думаю, что мы еще ближе к чему-то вроде этого в Typescript.
Вы можете посмотреть на предварительный компилятор, который будет генерировать такой код для вас до запуска Typescript, но это не похоже на тривиальную вещь.
С учетом этой проблемы я могу предложить только попробовать вместо Flow. В потоке вы можете просто сделать $Shape<IUser>
, чтобы сгенерировать тип, который вы хотите программно. Конечно, Flow отличается от Typescript многими большими и малыми способами, поэтому имейте это в виду. Поток не является компилятором, поэтому вы не получите таких вещей, как Enums и класс, реализующие интерфаки
Что вы хотите это
type Subset<T extends U, U> = U;
это гарантирует, что U является подмножеством T и возвращает U как новый тип. например:
interface Foo {
name: string;
age: number;
}
type Bar = Subset<Foo, {
name: string;
}>;
вы не можете добавлять в Bar новые свойства, которые не являются частью Foo, и вы не можете изменять типы несовместимым образом. это также работает рекурсивно на вложенных объектах.
Стоит отметить, что Partial<T>
, как предлагается в принятом ответе, делает все поля необязательными, что не обязательно то, что вам нужно.
Если вы хотите сделать некоторые поля обязательными (например, id
и email
), вам нужно объединить их с Pick
:
type UserWithOptionalPhone = Pick<IUser, 'id' | 'email'> & Partial<IUser>
Некоторое объяснение:
Что делает Pick
, так это то, что он позволяет вам задавать подмножество интерфейса лаконично (без создания совершенно нового интерфейса, повторяя типы полей, как это предлагается в других ответах), а затем позволяет вам использовать эти и только эти поля.
function hello1(user: Pick<IUser, 'id' | 'email'>) {
}
hello1({email: '@', id: 1}); //OK
hello1({email: '@'}); //Not OK, id missing
hello1({email: '@', id: 1, phone: '123'}); //Not OK, phone not allowed
Теперь это не совсем то, что нам нужно, поскольку мы хотим разрешить, но не требуем телефон. Чтобы сделать это, мы "объединяем" частичную и "выбранную" версии нашего типа, создавая тип пересечения, который затем будет иметь id
и email
качестве обязательных полей, а все остальное - в качестве необязательных - именно так, как мы этого хотели.
function hello2(user: Pick<IUser, 'id' | 'email'> & Partial<IUser>) {
}
hello2({email: '@', id: 1}); //OK
hello2({email: '@', id: 1, phone: '123'}); //OK
hello2({email: '@'}); //Not OK, id missing