TypeScript, как правило, не выполняет распространение соединений по свойствам. То есть, хотя каждое значение типа {foo: string | number}
должно присваиваться типу {foo: string} | {foo: number}
, компилятор не видит их как взаимно присваиваемые:
declare let unionProp: { foo: string | number };
const unionTop: { foo: string } | { foo: number } = unionProp; // error!
Кроме странных вещей, которые происходят при запуске изменяя свойства таких типов, компилятору вообще слишком много времени делать это, особенно когда у вас есть несколько свойств объединения. Соответствующий комментарий в microsoft / TypeScript # 12052 гласит:
. Этот тип эквивалентности имеет место только для типов с одним свойством и не является истинным в общем случае. Например, было бы неправильно считать { x: "foo" | "bar", y: string | number }
эквивалентным { x: "foo", y: string } | { x: "bar", y: number }
, поскольку первая форма допускает все четыре комбинации, тогда как вторая форма допускает только две указанные c единицы.
Так что это не ошибка как таковая, а ограничение: компилятор собирается потратить так много времени, играя в союзы, чтобы убедиться, что какой-то код допустим.
В TypeScript 3.5, добавлена поддержка для выполнения вышеуказанных вычислений специально для случая дискриминируемых союзов . Если у вашего объединения есть свойства, которые можно использовать для различения guish между членами объединения (что означает одноэлементные типы, такие как строковые литералы , цифра c литералы , undefined
или null
) в хотя бы некотором члене объединения , тогда компилятор проверит ваш код так, как вы этого хотите.
Вот почему это неожиданно работает, если вы измените T1A | T2A | T1B | T2B
на T1A | T2A
, и может быть возможным ответом на ваш вопрос:
function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
const x: T1A | T2A = { type, index: 3 }; // okay
return x;
}
Последний является дискриминационным объединением: свойство type
сообщает вам, какой член у вас есть. Но первое не так: свойство type
может различать guish между T1A | T1B
и T2A | T2B
, но нет свойства для дальнейшего деления. Нет, простое отсутствие index
или name
в типе не считается дискриминантом, поскольку значение типа T1A
может иметь свойство a name
; типы в TypeScript не точные .
Приведенный выше код работает, потому что компилятор может проверить, что x
имеет тип T1A | T2A
, а затем может проверить, что T1A | T2A
является подтипом из полного T1A | T2A | T1B | T2B
. Поэтому, если вас устраивает двухэтапный процесс, это возможное решение для вас.
Если вы хотите, чтобы T1A | T2A | T1B | T2B
был дискриминационным объединением, вам нужно изменить составные типы ' определения, чтобы действительно различать, по крайней мере, одним общим свойством. Например, вот так:
interface T1A {
type: 'type1';
index: number;
name?: never;
}
interface T1B {
type: 'type1';
name: string;
index?: never;
}
interface T2A {
type: 'type2';
index: number;
name?: never;
}
interface T2B {
type: 'type2';
name: number;
index?: never;
}
Добавляя эти необязательные свойства типа never
, теперь вы можете использовать type
, name
, и index
в качестве дискриминантов , Свойства name
и index
иногда будут undefined
, одноэлементный тип, который поддерживается для различных типов guish. И тогда это работает:
function hello(type: 'type1' | 'type2'): T1A | T2A | T1B | T2B {
return { type, index: 3 }; // okay
}
Так что это два варианта. Другой вариант, который всегда доступен в таких ситуациях, когда вы знаете, что что-то безопасно, а компилятор этого не делает, - это использование утверждения типа :
function hello2(type: 'type1' | 'type2') {
return { type, index: 3 } as T1A | T2A | T1B | T2B
}
Хорошо, надеюсь что помогает; удачи!
Детская площадка ссылка на код