Typescript - Как использовать дженерики для определения функции, которая возвращает индексируемые свойства объекта? - PullRequest
2 голосов
/ 08 января 2020

У меня проблемы с написанием правильных типов для простой функции.

Я хочу создать функцию, которая при получении клавиши (keyof any) или обратного вызова будет возвращать другую функцию. Эта функция принимает некоторые данные и возвращает другую клавишу.

Другими словами, toKey('propertyName') и toKey(value => value.propertyName) будут возвращать функцию value => value.propertyName. При вызове функция возвращает указанное значение указанного свойства, только если значение имеет тип string, number или symbol.

Вот пример:

function toKey(keyIdentity) { /* snippet */ }

interface IPerson {
    firstName: string,
    lastName: string,
    age: number,
    emails: string[]
};

const getKey1 = toKey((person: IPerson) => person.emails[0]);
const getKey2 = toKey('firstName');
const getKey3 = toKey('emails');

const person: IPerson = {
    firstName: 'Craig',
    lastName: 'Lipton',
    age: 46,
    emails: [
        '<email-1>',
        '<email-2>'
    ]
};

getKey1(person); // returns "<email-1>";
getKey2(person); // returns "Craig";
getKey3(person); // not allowed;

Я пытался использовать дженерики и перегрузки для достижения правильной типизации, но это очень быстро усложняется.

function toKey(key?: null): () => void;
function toKey<K extends keyof any>(key: K): <U extends keyof any>(item: Record<K, U>) => U;
function toKey<T extends (...args: any[]) => keyof any>(key: T): T;
function toKey(key: any): any {
    if (isNil(key)) {
        return noop;
    }

    return isFunction(key) ? key : (value: T) => value[key];
}

Есть ли более простой способ написать это?

Ответы [ 2 ]

1 голос
/ 08 января 2020

Если вы хотите быть явным в ваших типах, можно использовать встроенный тип PropertyKey:

// type PropertyKey = string | number | symbol

function toKey<K extends PropertyKey>(key: K): 
  <T extends Record<K, PropertyKey>>(t: T) => PropertyKey
function toKey<T>(fn: (t: T) => PropertyKey): (t: T) => PropertyKey
function toKey(key: PropertyKey | ((t: Record<PropertyKey, PropertyKey>) => PropertyKey)) {...}

Проверьте это:

toKey((person: IPerson) => person.emails[0])(person); // "<email-1>";
toKey('firstName')(person); // "Craig";
toKey('emails')(person); // error, emails prop is not string | number | symbol
toKey('lastName')({ firstName: "Lui" }); // error, object passed in has no key "lastName"

Код образец

0 голосов
/ 08 января 2020

Вы должны использовать это,

function toKey<T, K extends keyof T = keyof T>(key: K): (v: T) => T[K];
const getKey3 = toKey<IPerson>('emails');
const getKey3 = toKey<IPerson,"emails">('emails');

Пример

...