在打字稿中,缩小一般来说,对象属性(或子属性、子子属性等)的表观类型不会缩小对象本身的表观类型。唯一发生这种情况的情况是该对象属于受歧视联盟类型和您正在检查的属性是它的判别式.
就你而言,虽然state
的财产Box
属于受歧视联盟类型State
, Box
本身并不是一个受歧视的工会。这根本就不是一个工会。所以如果你有一个价值b
类型的Box
,那么即使检查b.state.type
会缩小b.state
,它不会缩小b
itself.
这是 TypeScript 的一个已知限制,并且已被多次报告。目前需要改进的开放问题是微软/TypeScript#42384。在过去,这些只是因为修复成本太高而被关闭(为了检查a.b.c.d.e
缩小范围不仅仅是a.b.c.d.e
,编译器可能需要合成新类型来表示对a.b.c.d
, a.b.c
, a.b
, and a
). A comment表明情况可能已经发生变化并且is可以实施。但目前还没有实施。
除非这种情况发生变化,否则我们就必须解决它。
有时对人们有用的一种解决方法是将现有对象复制到新对象中,其中选中的属性被挑出以进行显式复制。这将引导编译器完成合成缩小类型的逻辑。在你的情况下,它看起来像这样:
if (b.state.type === "B") {
const bRefined: Box<StateB> = { ...b, state: b.state } // okay
}
在这里我们有spread现有的b
对象转换为新的对象字面量,然后显式复制state
财产结束。现在编译器看到{...b, state: b.state}
属于类型Box<StateB>
.
否则,一般的解决方法是构建一个自定义类型保护函数它封装了“检查属性应该缩小父级”的概念。它可能看起来像这样:
function hasPropType<T extends object, K extends keyof T, V extends T[K]>(
obj: T, key: K, valGuard: (x: T[K]) => x is V): obj is T & Record<K, V> {
return valGuard(obj[key]);
}
这告诉编译器如果valGard(obj[key])
is true
, then hasPropType(obj, key, valGuard)
可以缩小类型obj
。在您的示例中,它可能如下所示:
if (hasPropType(b, "state", (x): x is StateB => x.type === "B")) {
const bRefined: Box<StateB> = b; // okay
}
当然,对于单次使用来说,这比仅仅使用您的isBoxB()
方法。但是,如果您发现自己经常进行此类检查,则可能不需要为每个检查构建单独的类型保护函数。
这里的一个折衷方案可能是编写一个比isBoxB
但不那么一般hasPropType
:
function isBox<K extends State['type']>(type: K, b: Box
): b is Box<Extract<State, { type: K }>> {
return b.state.type === type;
}
if (isBox("B", b)) {
const bRefined: Box<StateB> = b; // okay
}
Playground 代码链接