У меня есть тип, который принимает необязательный универсальный. Если предоставлен общий G, должно быть включено новое свойство типа G. Однако у меня возникла проблема, когда я делаю это в функции:
interface Message {
id: string;
message: string;
}
interface MessageWithContext<T> extends Message {
context: T;
};
// Optional generic. If generic provided,
// context must also provided.
type NotificationMessage<T = void> = T extends void
? Message
: MessageWithContext<T>;
interface Context1 {
status: 'GOOD';
}
interface Context2 {
status: 'BAD';
error: string;
}
type AllContext = Context1 | Context2;
// DOES NOT WORK. See error below.
function toMessage(context: AllContext): NotificationMessage<AllContext> {
return {
id: '123',
message: 'hello world',
context: context
};
}
Ошибка, которую я получаю:
Type '{ id: string; message: string; context: AllContext; }' is not assignable to type 'MessageWithContext<Context1> | MessageWithContext<Context2>'.
Type '{ id: string; message: string; context: AllContext; }' is not assignable to type 'MessageWithContext<Context2>'.
Types of property 'context' are incompatible.
Type 'AllContext' is not assignable to type 'Context2'.
Property 'error' is missing in type 'Context1' but required in type 'Context2'.(2322)
Соответствует ли этот подход вашим потребностям? Если вы не собираетесь использовать NotificationMessage<T> как дистрибутив над союзами в T, вам не нужен распределительный условный тип, и все начнет работать, когда вы измените его на обычный условный тип.
(Пожалуйста, отредактируйте код открытого текста, а не только ссылку на игровую площадку)
Спасибо @jcalz! Я исправил и ссылку, и открытый текст. И ДА, это исправляет это! Благодарю вас! Почему размещение квадратных скобок делает его недистрибутивным? Приведенная ссылка об этом не говорит.






Это определение NotificationMessage:
type NotificationMessage<T = void> = T extends void
? Message
: MessageWithContext<T>;
является распределительный условный тип, потому что тип T, который вы проверяете, является параметром универсального типа. Распределительные условные типы распределять — операция типа над союзы в соответствующем аргументе типа, так что NotificationMessage<A | B | C> будет оцениваться как NotificationMessage<A> | NotificationMessage<B> | NotificationMessage<C>:
type M = NotificationMessage<AllContext>
// type M = MessageWithContext<Context1> |
// MessageWithContext<Context2>
Это означает, что NotificationMessage<AllContext> сам по себе является типом объединения. И хотя значение { id: '123', message: 'hello world', context: context }мог можно считать присваиваемым такому типу объединения, компилятор не предпринимает никаких попыток это проверить. Это только пытается сделать это, если целевой тип — дискриминированный союз, а NotificationMessage<AllContext> не один (у него нет дискриминантного свойства... нет, подсвойства не учитываются, см. мс/ТС#18758). Значит есть ошибка.
Если вы действительно хотите, чтобы NotificationMessage<AllContext> был типом союза, мы могли бы посмотреть, как исправить toMessage(), чтобы он работал. Но вы, кажется, на самом деле не пытаетесь это сделать. Вероятно, вы были бы счастливы, если бы NotificationMessage<AllContext> был одним типом объекта со свойством context типа union.
Это означает, что вы не хотите, чтобы NotificationMessage<T> распространялось в T. Самый простой способ «отключить» дистрибутивность — заключить каждую сторону условия T extends U в квадратные скобки, чтобы получился одноэлементный кортежи:
type NotificationMessage<T = void> = [T] extends [void]
? Message
: MessageWithContext<T>;
См. этот ответ на вопрос о переполнении стека о том, как избежать дистрибутивности для получения дополнительной информации о том, почему это работает. Теперь у вас есть нераспространяемый тип:
type M = NotificationMessage<AllContext>
// type M = MessageWithContext<AllContext>
И теперь все просто работает:
function toMessage(context: AllContext): NotificationMessage<AllContext> {
return {
id: '123',
message: 'hello world',
context: context
}; // okay
}
Ваша ссылка на игровую площадку и код здесь не приводят к ошибке, которую вы показываете. Вы забыли собственность
id? Полезно иметь истинный минимальный воспроизводимый пример, чтобы люди могли немедленно приступить к решению проблемы, не создавая ее заново.