例如,我有一个非常简单的函数,它为某些命名对象属性设置“ true”(布尔值):
function setTrue<T, K extends keyof T>(host: T, property: K) {
if (typeof host[property] === 'boolean')
host[property] = true; // <-- TS2322: Type 'true' is not assignable to type 'T[K]'.
}
无法找出问题所在?在这种情况下,类型防护typeof host[property] === 'boolean'
似乎不起作用。谁知道在此示例中如何实现正确的静态类型检查?
我发现的唯一解决方法是如果T extends any
,但是在这种情况下根本不需要类型保护:
function setTrue<T extends any, K extends keyof T>(host: T, property: K) {
host[property] = true; // <-- Always Ok
}
答案 0 :(得分:0)
我认为有多种因素导致这种情况。
一个问题是,当要测试的属性键是变量时,编译器不会缩小类型保护范围;有关更多信息,请参见microsoft/TypeScript#10530。
另一个因素是,不能安全地完成对密钥类型未知的单例属性的读取和后续写入。参见microsoft/TypeScript#32693。编译器没有意识到property
是这两行中的完全相同的键。它所了解的就是所访问的属性具有相同的 type ,除非已经知道类型是单例的,否则这还不够严格。
最后,面对依赖于未指定的泛型类型参数(例如T
和K
)的值,编译器在控制流分析方面并不擅长。规范的问题是microsoft/TypeScript#13995。即使您测试property
是keyof T
的特定子类型,编译器也不会缩小K
参数的范围。充其量只能将property
的类型缩小到某个交叉点K & ...
,这可能会或可能不会有帮助。
在显示变通办法之前,请先在此处提出以下警告:从技术上讲,这并不安全。如果您的主机具有一个类型为布尔文字false
的属性,例如:
interface Foo {
bar: false
}
declare const foo: Foo;
然后您将遇到问题:
setTrue(foo, "bar");
foo.bar; // false or true?
您对setTrue()
的实现只是检查foo.bar
是否为boolean
类型。但是现在,我们通过将 type false
的属性设置为值true
来欺骗编译器。因此,foo.bar
现在在运行时为true
,而在您的IDE中为false
。
我将忽略这种皱纹,但它仍然存在,因此请小心。
因此,解决方法。到目前为止,最简单的方法就是使用type assertion并继续:
function setTrue<T, K extends keyof T>(host: T, property: K) {
if (typeof host[property] === 'boolean')
(host as any as Record<K, boolean>)[property] = true;
}
这里我们断言,如果host[property]
的类型为boolean
,那么我们会将host
视为Record<K, boolean>
:一个属性值为{{1 }},类型为boolean
。
最简单的方法是将其重构为一些较小的部分,使编译器可以自行验证或可以将其制成user-defined type guard,以使编译器按照我们希望的方式进行类型分析:>
K
仅允许在已知function setTrueKnownBoolean<K extends PropertyKey>(host: Record<K, boolean>, property: K) {
host[property] = true;
}
function isBooleanProperty<K extends keyof T, T>(
host: T, property: K): host is T & Record<K, boolean> {
return typeof host[property] === "boolean";
}
function setTrue2<K extends keyof T, T>(host: T, property: K) {
if (isBooleanProperty(host, property)) {
setTrueKnownBoolean(host, property);
}
}
键具有setTrueKnownBoolean
属性的host
个对象上调用函数boolean
,因此实现需要里面没有类型检查。函数property
充当类型保护,将isBooleanProperty
的类型从host
缩小到T
。现在T & Record<K, boolean>
可以通过组合其他两个函数来实现。
好的,希望能为您提供一些指导。祝你好运!