Я пытаюсь написать функцию, которая принимает объект и (строковый) ключ, а затем оперирует свойством объекта. Это легко:
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 еще раз: Пример игровой площадки - обновлено, чтобы попробовать подход, предложенный в комментарии, предложенном в @
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
На игровой площадке DefinitelyJustNumbers
показывает всплывающую подсказку с {a: number; whatevs: number}
- все, что я могу назначить number
, явно набирается как number
. Это фиксирует присвоение внутри тела функции, но не позволяет обнаружить тот факт, что nine
- это только подмножество числа и поэтому не должно быть разрешено.
CanAssignNumber
показывает всплывающую подсказку с {a: number; whatevs: any}
, правильно исключая nine
, потому что он не может быть назначен number
. Выглядит неплохо, но по-прежнему не исправляет присвоение внутри функции f2
.