如何解决打字稿:验证多余的键值,从函数返回
假设我正在这样做:
const user = User.findOne({where: {id}})
User.update({topics: (user.topics.indexOf('topicId') > -1) ? user.topics : Sequelize.fn('array_append',Sequelize.col('topics'),topicId) },{where: {id}})
结果为:
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'
TS完全可以使用,尽管给定的功能绝对不会返回type Func = () => Rec
const fn: Func = () => ({ a: 1,d: 4 })
类型。
同时使用Rec
给出
const fn: Func = () => ({ a: false,d: 4 })
因此,它实际上确实验证了返回的值。但是某种程度上,它并不关心多余的密钥。
为什么会发生这种情况,并且在这种情况下有什么方法不允许在返回值上使用多余的键?
解决方法
请注意,{a: 1,d: 4}
是Rec
类型的。 TypeScript 通常中的对象类型允许多余的属性,而不是exact。这与子类型化和可分配性有关是有充分的理由的。例如:
class Foo {a: string = ""}
class Bar extends Foo {b: number = 123}
console.log(new Bar() instanceof Foo); // true
请注意,每个Bar
都是一个Foo
,这意味着您不能说“所有Foo
个对象仅都有一个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处都有潜在的解决方案,但我我不确定是否以及何时将其转化为语言。尽管如此,您仍然有可能在即将发布的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
但是它们更复杂且更容易破坏,因为无论您尝试多少保护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,c: undefined
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。