您可以在签名中使用泛型maybeSetNumber()来表示它field属于通用属性键类型 ( K extends PropertyKey),并且属于在该键处target具有值的类型(使用实用程序类型):numberRecord<K, number>Record
function maybeSetNumber<K extends PropertyKey>(target: Record<K, number>, field: K) {
const num = maybeANumber();
if (num !== undefined) {
target[field] = num;
}
}
这将给出你想要的行为:
maybeSetNumber(foo, "a"); // error!
// ----------> ~~~
// Types of property 'a' are incompatible.
maybeSetNumber(foo, "b"); // okay
警告:TypeScript 并不完美,所以如果你开始使用比以下更窄的类型,这仍然会让你做一些不安全的事情number:
interface Oops { x: 2 | 3 }
const o: Oops = { x: 2 };
maybeSetNumber(o, "x"); // no error, but could be bad if we set o.x to some number < 1
也可以使签名使得上面的错误是 on"a"而不是 on foo。这种方式更复杂,并且至少需要一个类型断言,因为编译器不理解其中的含义:
type KeysMatching<T, V> = { [K in keyof T]: V extends T[K] ? K : never }[keyof T]
function maybeSetNumber2<T>(target: T, field: KeysMatching<T, number>) {
const num = maybeANumber();
if (num !== undefined) {
target[field] = num as any; // need a type assertion here
}
}
maybeSetNumber2(foo, "a"); // error!
// ----------------> ~~~
// Argument of type '"a"' is not assignable to parameter of type '"b"'.
maybeSetNumber2(foo, "b"); // okay
这不会遇到与 , 相同的Oops问题
maybeSetNumber2(o, "x"); // error!
但仍然可能存在围绕健全性的边缘情况。TypeScript 通常假设如果您可以X从属性中读取类型的值,那么您可以将类型的值写入X该属性。这很好,直到它不是。在任何情况下,其中任何一个都会比any.
Playground 代码链接