你想要exact types,它在 TypeScript 中并不存在。 TypeScript 中的对象类型被认为是“开放的”或“可扩展的”; {a?: string, b?: string, c?: string} 之类的类型仅指定 a、b 和 c 键处的属性类型。它根本不禁止其他键。所以像{a: "", b: "", c: "", d: 12345} 这样的值是有效的。因此Bar 是一个有效的Partial<Foo>。
请注意,这通常是一个理想的功能,因为它允许 interface 和 class 扩展:
interface X {
x: string;
}
interface Y extends X {
y: number;
}
这里,Y extends X 表示每个Y 是一个X。这直接导致您不满意的那种过度扩张的财产:
const y: Y = { x: "", y: 1 }
const x: X = y; // okay
有一些近似精确类型的变通方法,它们的表现可能足以满足您的目的,但都可以规避,因为编译器总是会让您将值扩大到不知道有问题的键的类型。
我首选的解决方法是将myMethod() generic 设置为Partial<Foo> 类似T 的类型part。您可以constrainT,这样其中的每个已知属性都必须是来自Foo 的属性,如果有任何额外的属性,它们必须是never。像这样:
mymethod<T extends { [K in keyof T]: K extends keyof Foo ? Foo[K] : never }>(
part: T
) {
console.log(part);
}
并测试一下:
obj.mymethod({a: ""}) // okay
const bar: Bar = { a: "", d: "" };
obj.mymethod(bar); // error!
// Types of property 'd' are incompatible.
万岁,它可以防止Bar!如果您将对象交给mymethod,那就太好了,已知 有多余的属性。但没有什么能阻止这一点:
const partialFoo: Partial<Foo> = bar; // valid assignment
obj.mymethod(partialFoo); // no error!
这里,partialFoo 被显式注释为Partial<Foo>,bar 被分配给它。之所以成功,是因为 TypeScript 中的类型是开放的,而不是精确的。而且编译器已经完全忘记了partialFoo来自Bar;它只将其视为Partial<Foo>。所以编译器的 known 键没问题,而有问题的键是编译器 unknown 的,所以它不会捕获它。
在这一点上,我想说你可能想退后一步,考虑使用 TypeScript 的类型系统而不是反对它。如果 TypeScript 将对象类型视为开放的,那么您的代码也应该这样做,只对已知键进行显式操作。想象一下,我们有一个函数pluck(),它产生一个只有我们指定的键的对象:
function pluck<T, K extends keyof T>(t: T, ...k: K[]): Pick<T, K> {
return k.reduce((a, k) => (k in t && (a[k] = t[k]), a), {} as Pick<T, K>);
}
然后创建一个 mymethod() 的版本,在对其进行操作之前,plucks 来自 part 的 a、b 和 c 属性:
mySafeMethod(part: Partial<Foo>) {
this.mymethod(pluck(part, "a", "b", "c"));
}
在这种情况下,如果你传入一个Bar,就不再是问题了:
obj.mySafeMethod(bar); // { a: "" };
运行时代码预计part 可能具有比a、b 和c 更多的属性,并显式忽略它们,而不是例如使用Object.keys() 或@ 迭代所有现有键987654374@。这可能是不那么惯用的 JavaScript,但它确实使编译器更容易处理。
好的,希望其中一个想法对您有所帮助;祝你好运!
Playground link to code
更新
TypeScript 并不能真正“获取”确切的类型,尤其是未指定的泛型类型,例如您在 mymethod 的实现中看到的类型。所以在mymethod 内部,编译器对part 了解不多。有办法解决这个问题......一种更丑陋的方法是将确切类型与开放类型相交,这样编译器至少可以知道T 是Partial<Foo> 以及它无法验证的其他内容:
mymethod<T extends Partial<Foo> &
{ [K in keyof T]: K extends keyof Foo ? Foo[K] : never }
>(
part: T
) {
console.log(part);
part.b?.toUpperCase(); // okay
}
这对你有用吗?
Playground link to code