是否可以将泛型类型限制为仅允许已知属性?

2024-04-06

如果向函数提供的对象具有太多属性,则会出现错误:

type Options = {
    str: "a" | "b",
}

function foo(a: Options) {
    return a.str;
}

const resultA = foo({
    str: "a",
    extraOption: "errors as expected",
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^------ Object literal may only specify known properties.
});

这个不错,我想要这个但因为我知道我将根据其输入返回什么类型,所以我想让该函数变得通用,如下所示:

function bar<T extends Options>(a: T) {
    return a.str as T["str"];
}

但现在输入上允许使用额外的属性。

const resultB = bar({
    str: "a",
    extraOption: "no error!?",
});

有什么办法可以限制这个吗?


TypeScript 中的对象类型不是“密封的”;允许有多余的属性。这使得各种好东西成为可能,例如接口和类扩展,并且只是 TypeScript 的一部分结构类型系统 https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html#structural-type-system。多余的属性总是有可能潜入的,所以你应该确保你的foo() or bar()如果函数参数具有这样的属性,实现不会做任何可怕的事情。如果您通过类似的方式迭代属性the Object.keys() method https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys, 你应该not假设结果只是已知的密钥。 (这就是为什么Object.keys(obj)回报string[] https://stackoverflow.com/q/55012174/2887218)

当然,扔掉多余的财产通常表明存在问题。如果您通过了对象字面量 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names直接传递给函数,那么该文字上的任何额外属性很可能会被完全忽略和遗忘。这可能表明存在错误,因此对象文字接受多余属性的检查 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks:

function foo(a: Options) {
    return a.str;
}

const resultA = foo({
    str: "a",
    extraOption: "errors as expected", // error
});

为什么这种情况会因为类似的事情而消失

function bar<T extends Options>(a: T) {
    return a.str as T["str"];
}
const resultB = bar({
    str: "a",
    extraOption: "no error!?",
});

是因为泛型函数确实有可能跟踪这些额外的属性:

function barKT<T extends Options>(a: T) {
    return { ...a, andMore: "hello" }
}
const resultKT = barKT({ str: "a", num: Math.PI });
console.log(resultKT.num.toFixed(2)) // "3.14"

但在你的版本中bar()你只是返回str财产,所以多余的财产确实会消失。因此,您希望在多余的属性上出现错误,但您没有得到错误。


但这提出了一个问题:为什么bar()通用的?如果您不想允许多余的属性并且您只关心其中一个str财产,那么就没有明显的动机T extends Options。如果您只想尽可能缩小类型T["str"],那么实际上你只想那部分输入的通用性:

function barC<S extends Options["str"]>(a: { str: S }) {
    return a.str;
}

const resultC = barC({
    str: "a",
    extraOption: "error", // error here
});

但如果你真的需要T extends Options出于某种原因,您可以通过将函数输入设为映射类型 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html其中多余的属性将其属性值类型映射到the never type https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-never-type。由于没有类型的值never,传入的对象字面量无法满足该要求,并且它们将生成错误:

function barD<T extends Options>(a: 
  { [K in keyof T]: K extends keyof Options ? T[K] : never }
) {
    return a.str
}

const resultD = barD({
    str: "a",
    extraOption: "error", // error here
});

Hooray!


同样,虽然这会阻止过多的财产,但并不能绝对阻止它们。结构子类型要求您可以扩大{str: "a" | "b", extraOption: string} to {str: "a" | "b"}:

const someValue = {
    str: "a",
    extraOption: "error",
} as const;

const someOptions: Options = someValue; // okay

barC(someOptions); // no error
barD(someOptions); // no error

同样,您应该确保您的实现不会假设多余的属性是不可能的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以将泛型类型限制为仅允许已知属性? 的相关文章

随机推荐