【发布时间】:2021-02-03 12:23:32
【问题描述】:
我正在尝试编写一个函数,该函数接受一个对象和一个(字符串)键,然后对该对象的属性进行操作。这很简单:
function f<T extends any, K extends keyof T>(obj: T, key: K) {
const prop = obj[key]; // prop is typed as T[K]
}
我想在编译时根据T[K] 的类型限制传递给调用的键。我试过这个:
function f<T extends any, K extends keyof T>(obj: T, key: T[K] extends number ? K : never) {
obj[key] = 5; // error, "is not assignable to" etc
}
prop 被输入为 T[T[K] extends number ? K : never],在我看来它应该折叠为 number,但它没有。
我的目标是确保在函数内部将obj[key] 键入为number,并且还将诸如f({a: true}, "a") 之类的调用标记为错误。这可能吗?我想我可能需要将约束从函数参数声明移动到泛型参数声明,但我无法弄清楚语法。
ETA 再次:Playground example -- 更新以尝试@reactgular 在评论中建议的方法:
type AssignableKeys<T, ValueType> = {
[Key in keyof T]-?: ValueType extends T[Key] | undefined ? Key : never
}[keyof T];
type PickAssignable<T, ValueType> = Pick<T, AssignableKeys<T, ValueType>>;
type OnlyAssignable<T, ValueType> = {
[Key in AssignableKeys<T, ValueType>]: ValueType
};
interface Foo {
a: number;
b: string;
nine: 9;
whatevs: any;
}
type FooNumberKeys = AssignableKeys<Foo, number>; // "a" | "whatevs"
type CanAssignNumber = PickAssignable<Foo, number>; // { a: number; whatevs: any; }
type DefinitelyJustNumbers = OnlyAssignable<Foo, number>; // { a: number; whatevs: number; }
function f1<T>(obj: OnlyAssignable<T, number>, key: keyof OnlyAssignable<T, number>) {
obj[key] = Math.random(); // Assignment is typed correctly, good
}
function f2<T extends object, K extends keyof PickAssignable<T, number>>(obj: T, key: K) {
obj[key] = Math.random(); // Uh oh, Type 'number' is not assignable to type 'T[K]'.(2322)
}
declare const foo: Foo;
f1(foo, "a"); // Allowed, good
f1(foo, "whatevs"); // Allowed, good
f1(foo, "nine"); // Uh oh, should error, but doesn't!
f1(foo, "b"); // Error, good
f2(foo, "a"); // Allowed, good
f2(foo, "whatevs"); // Allowed, good
f2(foo, "nine"); // Error, good
f2(foo, "b"); // Error, good
在 Playground 中,DefinitelyJustNumbers 显示{a: number; whatevs: number} 的工具提示——我可以将number 分配给的任何内容都明确键入为number。这修复了函数体内的赋值,但未能检测到 nine 只是数字的子集,因此不应被允许。
CanAssignNumber 显示{a: number; whatevs: any} 的工具提示,正确排除了nine,因为它不能分配给number。这看起来不错,但仍然不能修复函数 f2 内的分配。
【问题讨论】:
-
@Reactgular 我更新了我的帖子来解决这个问题,但我忘记了编辑帖子可能没有通知你。所以:平!
标签: typescript generics conditional-types