TypeScript:验证从函数返回的值上的多余键 [英] typescript: validate excess keys on value, returned from function
问题描述
假设我正在做此操作:
type Keys = 'a' | 'b' | 'c'
type Rec = { [K in Keys]?: number }
let rec: Rec = { a: 1, d: 4 }
结果为:
Type '{ a: number; d: number; }' is not assignable to type 'Rec'.
Object literal may only specify known properties, and 'd' does not exist in type 'Rec'
所以它不允许对象上有任何额外的键。
但如果我执行以下操作:
type Func = () => Rec
const fn: Func = () => ({ a: 1, d: 4 })
TS完全可以接受它,尽管给定的函数肯定返回的不是Rec
类型。
,同时使用const fn: Func = () => ({ a: false, d: 4 })
提供
Type 'boolean' is not assignable to type 'number | undefined'.(2322)
input.tsx(63, 12): The expected type comes from property 'a' which is declared here on type 'Rec'
所以它实际上确实验证了返回值。但不知何故并不关心多余的密钥。
为什么会发生这种情况,在这种情况下有没有办法不允许在返回值上使用额外的键?
推荐答案
请注意,{a: 1, d: 4}
是Rec
类型的。TypeScript中的对象类型通常允许多余的属性,并且不是microsoft/TypeScript#12936中要求的精确对象类型。这与子类型和可分配性有关,这是有充分理由的。例如:
class Foo {a: string = ""}
class Bar extends Foo {b: number = 123}
console.log(new Bar() instanceof Foo); // true
注意,每个Bar
都是一个Foo
,这意味着您不能在不阻止类或接口继承和扩展的情况下说";allFoo
对象只有有一个a
属性。由于interface
的工作方式相同,而且TypeScript的类型系统是structural而不是名义的,因此您甚至不需要声明一个Bar
类型就可以使其存在:
interface Foo2 {a: string};
// interface Bar2 extends Foo2 {b: number};
const bar2 = {a: "", b: 123 };
const foo2: Foo2 = bar2; // okay
因此,不管是好是坏,我们只能使用类型系统,在该系统中,额外的属性不会破坏类型兼容性。
当然,这可能是错误的来源。因此,在将全新的对象文字显式分配给需要特定对象类型的位置的情况下,有excess property checks的表现得好像的类型是精确的一样。这些检查仅在特定情况下进行,如您的第一个示例所示:
let rec: Rec = { a: 1, d: 4 }; // excess property warning
但函数返回值目前不在这些情况中。在进行任何额外的属性检查之前,返回值的类型会被加宽。GitHub有一个相当老的开放问题,microsoft/TypeScript#241它建议应该更改这个问题,这样函数的返回值就不会以这种方式扩大,甚至在microsoft/TypeScript#40311中实现了一个潜在的修复,但是它被关闭了,所以它可能永远不会出现在语言中。
一般来说,没有任何完美的方法可以抑制多余的属性。我的建议是只接受对象可能有多余的键,并确保在这种情况下您编写的任何代码都不会崩溃。您可以执行一些阻止过量属性的操作,例如:
// annotate return type explicitly
const fn2: Func = (): Rec => ({ a: 1, d: 4 }) // excess property warning
// use a generic type that gets mad about excess properties
const asFunc = <T extends Rec & Record<Exclude<keyof T, keyof Rec>, never>>(
cb: () => T
): Func => cb;
const fn3 = asFunc(() => ({ a: 1, d: 4 })); // error! number is not never
但它们更复杂、更容易破解,因为无论您如何保护Func
类型:
const someBadFunc = () => ({ a: 1, d: 4 });
const cannotPreventThis: Rec = someBadFunc();
编写预期额外属性的代码通常涉及保持已知键的数组。所以不要这样做:
function extraKeysBad(rec: Rec) {
for (const k in rec) {
const v = rec[k as keyof Rec]; // bad assumption that k is keyof Rec
console.log(k + ": " + v?.toFixed(2))
}
}
const extraKeys = {a: 1, b: 2, d: "four"};
extraKeysBad(extraKeys); // a: 1.00, b: 2.00, RUNTIME ERROR! v.toFixed not a function
改为执行此操作:
function extraKeysOkay(rec: Rec) {
for (const k of ["a", "b", "c"] as const) {
const v = rec[k];
console.log(k + ": " + v?.toFixed(2))
}
}
extraKeysOkay(extraKeys); // a: 1.00, b: 2.00, c: undefined
这篇关于TypeScript:验证从函数返回的值上的多余键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!