Функция Typescript, где объект имеет свойство типа string, определяемое другим параметром - PullRequest
1 голос
/ 30 апреля 2020

Я хочу создать функцию TypeScript, которая принимает объект и свойство внутри этого объекта, для которого значение равно string. Использование <T, K extends keyof T> позволяет убедиться, что в качестве значений свойства разрешены только ключи T, но я не могу сузить его, так что ключ также должен указывать на свойство типа string. Возможно ли это?

Я пробовал это:

function getKey<T extends {K: string}, K extends keyof T>(item: T, keyProperty: K): string {
     return item[keyProperty];
}

Но это просто говорит Type 'T[K]' is not assignable to type 'string'. Почему ограничение T extends {K: string} не гарантирует, что T[K] на самом деле является string, или, скорее, что поставляемый K должен удовлетворять условию, чтобы T[K] был string?

Чтобы было ясно, я хочу иметь возможность вызывать эту функцию следующим образом:

getKey({foo: 'VALUE', bar: 42}, 'foo') => return 'VALUE';

getKey({foo: 'VALUE', bar: 42}, 'bar') => should not be allowed since 'bar' is not a string property of the supplied object

getKey({foo: 'VALUE', bar: 'ANOTHER VALUE'}, 'bar') => return 'ANOTHER VALUE'

Ответы [ 2 ]

2 голосов
/ 01 мая 2020

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');

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

1 голос
/ 30 апреля 2020
function getKey<T>(item: T, keyProperty: {[K in keyof T]: T[K] extends string ? K : never}[keyof T]): string {
    return <string> <unknown> item[keyProperty];
}

Немного громоздко, но, да ... это работает. :)

...