我想使用数组过滤谓词函数
例如
const isNotEmptyName = <T extends { name: string | null }>(value: T): value is { name: string } => Boolean(value.name);
但是,在这种情况下我有一个错误
A type predicate's type must be assignable to its parameter's type.
Type '{ name: string; }' is not assignable to type 'T'.
'{ name: string; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ name: string | null; }'.
我做错了什么?
你并没有真正做错什么,但是 TypeScript 要求对于 val is Type
形式的
类型谓词,
val
的类型必须首先可分配给 Type
,因此 val is Type
是被视为“缩小”。 TypeScript 抱怨 value is {name: string}
的原因是,有人可能会对名称属性比 isNotEmptyName()
窄的值调用 string | null
,例如使用字符串 文字类型而不是
string
:interface Foo {
name: "hello" | "goodbye" | null;
age: number;
}
declare const foo: Foo;
isNotEmptyName(foo);
在这种情况下,
{name: string}
被认为是错误的,因为
string
不是 "hello" | "goodbye" | null
的缩小。
最简单的解决方法是从函数中完全删除泛型,因为没有泛型它也会按预期运行:const isNotEmptyName = (value: { name: string | null }):
value is { name: string } =>
Boolean(value.name)
它可以编译,因为
{name: string}
比
{name: string | null}
更窄,当您使用它时,编译器会计算出输入的结果类型应该是什么:if (isNotEmptyName(foo)) {
foo // const foo: Foo & { name: string; }
foo.name.toUpperCase(); // okay
}
您可以看到
foo
已从
Foo
缩小到 交点
Foo & {name: string}
,这意味着 foo
必须是这两者。事实上,foo.name
的类型已缩小为没有"hello" | "goodbye"
的null
,您可以安全地使用toUpperCase()
取消引用它。
如果由于某种原因你需要保持它的通用性,你可以通过手动编写类型谓词来引用交集来获得相同的效果:const isNotEmptyName = <T extends { name: string | null }>(value: T):
value is T & { name: string } =>
Boolean(value.name)
可以编译,因为对于任何
T
(包括
T & U
),U
绝对可以分配给 {name: string}
。当你调用它时它的作用是一样的:if (isNotEmptyName(foo)) {
foo // const foo: Foo & { name: string; }
foo.name.toUpperCase(); // okay
}
Playground 代码链接