TypeScript:验证从函数返回的值上的多余键 [英] typescript: validate excess keys on value, returned from function

查看:20
本文介绍了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 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'

所以它实际上确实验证了返回值。但不知何故并不关心多余的密钥。

Demo

为什么会发生这种情况,在这种情况下有没有办法不允许在返回值上使用额外的键?

推荐答案

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

Playground link to code

这篇关于TypeScript:验证从函数返回的值上的多余键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆