Если SomeType
не является конкретно объединением , в котором один из членов является узким типом, который вы хотите, это не сработает. Давайте сначала посмотрим на счастливый случай, когда SomeType
является таким объединением, хотя, очевидно, это не ваш случай использования:
type SomeType = {
a: string,
b: string,
c: {
c: string
}
} | { foo: string } | { bar: string };
Здесь SomeType
явно является объединением тип, который вы хотите, и два несвязанных типа. Затем, когда вы присваиваете значение obj
, помеченное как SomeType
,
const obj: SomeType = {
a: "a",
b: "b",
c: {
c: "c"
}
};
, тип typeof obj
, захваченный в этой точке, является тем типом, который вы хотите:
type MyType = typeof obj;
/* type MyType = {
a: string;
b: string;
c: {
c: string;
};
} */
тип MyType
не содержит {foo: string}
или {bar: string}
. Это связано с тем, что компилятор использует анализ типов на основе потока управления для сужения типов объединения при присваивании (см. microsoft / TypeScript # 8010 для комментариев PR, реализующих это).
Но при назначении нет сужения потока управления на основе несоединения типизированных переменных. В какой-то момент команда TS, похоже, считала , учитывая ее , и есть открытое предложение (см. microsoft / TypeScript # 16976 ) для сужения потока управления для типов, не являющихся объединением, но на данный момент это просто не часть языка.
Это означает, что если ваш SomeType
шире, чем вы хотели, но не объединение, содержащее элемент, вы застряли. После того, как вы аннотируете переменную такого широкого типа, все присваивания этой переменной забудут что-либо более конкретное c о текущем значении этого типа.
Здесь нужно сделать не для аннотирования типа переменной. Единственная причина, по которой вы должны аннотировать переменную более широким типом, чем у присваиваемого вами значения, заключается в том, что вы хотите позднее изменить содержимое переменной, чтобы она стала другим обитателем этого более широкого типа. Например, если SomeType
задано так:
interface SomeType {
a: string | number;
b: unknown;
c: object;
}
Тогда единственная веская причина, по которой вы должны аннотировать следующее объявление как SomeType
:
const obj: SomeType = {
a: "a",
b: "b",
c: {
c: "c"
}
};
, будет, если вы планировали сделать это позже:
obj.a = 123;
obj.b = () => 456;
obj.c = [789];
Если вы не планируете использовать этот более широкий тип, то вы должны позволить компилятору выводить типа obj
. Если вы обеспокоены тем, что это позволит вам присвоить что-то несовместимое с SomeType
, то вы можете использовать вспомогательную функцию, которая действует как сужающая аннотация:
const annotateAs = <T>() => <U extends T>(u: U) => u;
Этот помощник функцию можно использовать так:
const asSomeType = annotateAs<SomeType>();
Теперь asSomeType()
- это функция, которая будет возвращать свой вход без расширения, но выдаст ошибку, если этот вход не может быть назначен на SomeType
:
const oops = asSomeType({
a: "a",
b: "b",
c: "c" // error! string is not an object
})
Теперь вы можете написать назначение obj
следующим образом:
const obj = asSomeType({
a: "a",
b: "b",
c: {
c: "c"
}
});
Это успешно, так что obj
определенно является SomeType
, но теперь тип obj
это :
type MyType = typeof obj;
/* type MyType = {
a: string;
b: string;
c: {
c: string;
};
} */
, как вы хотели.
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код