除非SomeType 是专门的union,其中一个成员是您想要的窄类型,否则这是行不通的。让我们先看看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}。这是因为编译器在分配时使用control flow based type analysis 来缩小联合类型(请参阅microsoft/TypeScript#8010 了解实现此功能的 PR 说明)。
但是在赋值时没有基于控制流的非联合类型变量的缩小。在某一时刻,TS 团队似乎是considering it,并且有一个公开的建议(参见microsoft/TypeScript#16976)来缩小非联合类型的控制流,但目前它还不是语言的一部分。
这意味着如果您的 SomeType 比您想要的宽,但不是特别包含该元素的联合,您就会被卡住。一旦您将变量注释为该宽类型,对该变量的所有赋值都会忘记有关该类型当前值的任何更具体的内容。
这里正确的做法是不来注释变量的类型。您应该将变量注释为比您分配的值更广泛的类型的唯一原因是,如果您想稍后改变变量的内容以使其成为该更广泛类型的其他居民。例如,如果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;
};
} */
如你所愿。
好的,希望对您有所帮助;祝你好运!
Playground link to code