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
同样,您应该确保您的实现不会假设多余的属性是不可能的。