In, {K: string}
, K
в названии строкового литерала . Это то же самое, что если бы вы написали {"K": string}
:
type Oops = { "K": string };
// type Oops = { K: string; }
Поскольку вы хотите, чтобы K
был типом ключа, вам нужно использовать сопоставленный тип , который выполняет итерацию некоторого объединения ключей ... или {[P in K]: string}
, или эквивалентного Record<K, string>
, используя Record<K, T>
служебный тип :
function getKey<T extends { [P in K]: string }, K extends keyof T>(
item: T,
keyProperty: K
): string {
return item[keyProperty]; // no error
}
И ваш вызывающий код ведет себя (в основном) так, как вы ожидаете:
getKey({ foo: 'VALUE', bar: 42 }, 'foo'); // okay
getKey({ foo: 'VALUE', bar: 42 }, 'bar'); // error!
// ----------------> ~~~
// number is not assignable to string
getKey({ foo: 'VALUE', bar: 'ANOTHER VALUE' }, 'bar'); // okay
Я говорю "в основном", потому что вполне возможно, что вы ожидаете, что ошибка во второй строке будет соответствовать значению 'bar'
, переданному в для keyProperty
, в то время как на самом деле происходит ошибка в свойстве bar
значения, переданного для item
.
С немного большим обманом вы можете добиться этого:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
function getKey2<T extends { [P in K]: string }, K extends KeysMatching<T, string>>(
item: T,
keyProperty: K
): string {
return item[keyProperty];
}
Здесь мы ограничиваем K
не только keyof T
, но и указанными c ключами T
, значения которых имеют тип string
. Мы делаем это с помощью нашего собственного KeysMatching<T, V>
типа утилиты. Это не меняет того, какие значения будут действительными, но смещается, когда компилятор жалуется, когда что-то неверно:
getKey2({ foo: 'VALUE', bar: 42 }, 'foo');
getKey2({ foo: 'VALUE', bar: 42 }, 'bar'); // error!
// -----------------------------> ~~~~~
// "bar" is not "foo"
getKey2({ foo: 'VALUE', bar: 'ANOTHER VALUE' }, 'bar');
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код