typescript:验证从函数返回的值上的多余键
假设我正在这样做:typescript:验证从函数返回的值上的多余键,typescript,Typescript,假设我正在这样做: 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 exis
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中的对象类型通常允许多余的属性,但不允许。这与子类型和可分配性有很好的关系。例如:
class Foo {a: string = ""}
class Bar extends Foo {b: number = 123}
console.log(new Bar() instanceof Foo); // true
请注意,每个Bar
都是Foo
,这意味着您不能说“所有Foo
对象只有a
属性”,而不阻止类或接口继承和扩展。而且由于接口
的工作方式相同,而且由于TypeScript的类型系统是非标称的,因此您甚至不必声明条形图
类型即可使其存在:
interface Foo2 {a: string};
// interface Bar2 extends Foo2 {b: number};
const bar2 = {a: "", b: 123 };
const foo2: Foo2 = bar2; // okay
因此,无论是好是坏,我们都被困在一个类型系统中,因此额外的属性不会破坏类型兼容性
当然,这可能是错误的来源。因此,如果您显式地将一个全新的对象文字指定给一个需要特定对象类型的位置,则会出现一些行为与类型完全相同的情况。这些检查仅在特定情况下进行,如第一个示例所示:
let rec: Rec = { a: 1, d: 4 }; // excess property warning
但是函数的返回值目前不是这些情况之一。在进行任何多余的属性检查之前,返回值的类型会变宽。有一个很老的开放GitHub问题,它建议应该改变这一点,以使函数的返回值不会以这种方式扩大,甚至在上有一个潜在修复的实现,但我不确定它是否以及何时会进入语言。尽管如此,您仍有可能在即将发布的TS版本中看到变化
一般来说,没有任何完美的方法可以抑制多余的属性。我的建议是接受对象可能有多余的键,并确保在这种情况下,您编写的任何代码都不会中断。您可以采取一些措施阻止过度的属性,例如:
// 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
编写预期额外属性的代码通常需要保留一组已知键。所以不要这样做:
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
注意
{a:1,d:4}
属于Rec
类型。TypeScript中的对象类型通常允许多余的属性,但不允许。这与子类型和可分配性有很好的关系。例如:
class Foo {a: string = ""}
class Bar extends Foo {b: number = 123}
console.log(new Bar() instanceof Foo); // true
请注意,每个Bar
都是Foo
,这意味着您不能说“所有Foo
对象只有a
属性”,而不阻止类或接口继承和扩展。而且由于接口
的工作方式相同,而且由于TypeScript的类型系统是非标称的,因此您甚至不必声明条形图
类型即可使其存在:
interface Foo2 {a: string};
// interface Bar2 extends Foo2 {b: number};
const bar2 = {a: "", b: 123 };
const foo2: Foo2 = bar2; // okay
因此,无论是好是坏,我们都被困在一个类型系统中,因此额外的属性不会破坏类型兼容性
当然,这可能是错误的来源。因此,如果您显式地将一个全新的对象文字指定给一个需要特定对象类型的位置,则会出现一些行为与类型完全相同的情况。这些检查仅在特定情况下进行,如第一个示例所示:
let rec: Rec = { a: 1, d: 4 }; // excess property warning
但是函数的返回值目前不是这些情况之一。在进行任何多余的属性检查之前,返回值的类型会变宽。有一个很老的开放GitHub问题,它建议应该改变这一点,以使函数的返回值不会以这种方式扩大,甚至在上有一个潜在修复的实现,但我不确定它是否以及何时会进入语言。尽管如此,您仍有可能在即将发布的TS版本中看到变化
一般来说,没有任何完美的方法可以抑制多余的属性。我的建议是接受对象可能有多余的键,并确保在这种情况下,您编写的任何代码都不会中断。您可以采取一些措施阻止过度的属性,例如:
// 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
编写预期额外属性的代码通常需要保留一组已知键。所以不要这样做:
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