微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

打字稿:验证多余的键值,从函数返回

如何解决打字稿:验证多余的键值,从函数返回

假设我正在这样做:

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 })

因此,它实际上确实验证了返回的值。但是某种程度上,它并不关心多余的密钥。

Demo

为什么会发生这种情况,并且在这种情况下有什么方法不允许在返回值上使用多余的键?

解决方法

请注意,{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

Playground link to code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?