提问人:Don McNamara 提问时间:7/8/2023 最后编辑:Don McNamara 更新时间:7/8/2023 访问量:35
类型缩小行为不起作用,具体取决于所涉及的类型
Type narrowing behavior not working depending on types involved
问:
我有一些导致编译器错误的类型缩小代码。当我对缩小范围所涉及的类型进行看似微小的更改时,编译器会检查通过。这些类型有些重叠,并且涉及可选属性。
是否有任何有关类型缩小的规则可以解释此行为?
当编译失败时,应该缩小范围的类型似乎没有被缩小 -- 在本例中是 。对成功编译的类型进行更改时,类型为 。Pet
Pet & Dog
我在生产代码中遇到了这个问题,但创建了一个简单的复制品。
我在操场上检查了以前的 TypeScript 版本,行为似乎是一样的,所以我认为这不是新的编译器行为。
interface Pet {
name: string; // remove this or make it nullable and it compiles.
isFriendly: boolean; // remove this it compiles.
}
interface Dog {
name: string; // remove this and it compiles
ownerName?: string; // make this non-nullable and it compiles
// isHappy: boolean; // Add any other non-nullable property and it compiles
}
function isDog(value: any): value is Dog {
return true;
}
// change the type here to Pet | Dog, or Pet & Dog and it compiles
const testAnimal: Pet = {} as any;
if (isDog(testAnimal)) {
// when the compiler error occurs, the type of `testAnimal` is `Pet` here, not `Dog` and not `Pet & Dog`
console.log(testAnimal.ownerName); // <-- Compiler error here: Property 'ownerName' does not exist on type 'Pet'
}
答:
1赞
jsejcksn
7/8/2023
#1
用户定义的类型保护函数只能具有缩小范围的谓词返回类型,它们不能扩大或以其他方式任意改变操作数参数的类型。
因此,类型 guard 的谓词必须是输入类型和 的交集,因此:Dog
declare function isDog<T>(value: T): value is T & Dog;
在这里,使用泛型类型参数 for 是合适的,以便保留该类型信息,而无需事先知道它(编译器会推断它)——您甚至可以用另一种更广泛的类型来约束它(根据您的用例——即使是类似的东西也总比没有好)。value
object
下面是使用您提供的数据的完整示例:
function hasOptionalProperty<
O extends object,
K extends PropertyKey,
V,
>(
obj: O,
prop: K,
valueValidatorFn: (value: unknown) => value is V,
): obj is O & Partial<Record<K, V>> {
return (
!(prop in obj) ||
valueValidatorFn((obj as Record<K, unknown>)[prop])
);
}
interface Pet {
name: string;
isFriendly: boolean;
}
interface Dog {
name: string;
ownerName?: string;
}
function isDog<T>(value: T): value is T & Dog {
return (
typeof value === "object" && value !== null &&
"name" in value && typeof value.name === "string" &&
hasOptionalProperty(
value,
"ownerName",
(value): value is string => typeof value === "string",
)
);
}
const testAnimal: Pet = { name: "Ani", isFriendly: true };
if (isDog(testAnimal)) {
console.log(testAnimal.ownerName); // Ok
//^? const testAnimal: Pet & Dog
}
评论
0赞
jsejcksn
7/8/2023
^@DarrylNoakes我不确定你的意思:基本类型保护已经与完整示例分开。你能澄清一下吗?
0赞
Darryl Noakes
7/8/2023
对不起,几秒钟后我意识到那一行实际上是所有改变的东西,并删除了我的评论。
0赞
Darryl Noakes
7/8/2023
我真的很想听听你对更改属性如何影响它的解释。例如,当按照OP所述进行更改时,两种类型的相对位置“狭隘性”如何变化?我的评论几乎完全基于观察和直觉,对 TS 内部和怪癖和边缘情况有一些了解。
1赞
jsejcksn
7/8/2023
^@DarrylNoakes我不确定我是否理解你的意思,但如果你参考 OP 对两个接口属性的评论,那么我同意这似乎很奇怪——了解源代码控制编译器中类型保护评估的人可能需要回答这个问题。
评论
Dog
Pet
testAnimal
Pet
isFriendly
Pet
Dog
Dog
Pet
Pet
Dog
name
Pet
testAnimal
Dog
Pet
Dog
ownerName
Dog
isFriendly
Dog
testAnimal
Pet & Dog
Dog
testAnimal
Pet | Dog
ownerName