Как установить интерфейс для существующей, но в настоящее время неизвестной пары ключ / значение? - PullRequest
0 голосов
/ 08 мая 2019

У меня есть следующий интерфейс:

interface Item {
    _id: string;
    name: string;
    amount: number;
    type: 'a' | 'b' | 'c';
    value: string;
}

Теперь я хочу создать универсальный метод для обновления одного значений, который может быть любым из пяти .

Упрощенно, функция выглядит так:

patch(item: Item, key: any, value: any): Item {
    return {
        ...item,
        [key]: value
    };
}

  • Что использовать для key?
  • Что использовать для value?

Ответы [ 2 ]

2 голосов
/ 08 мая 2019

Ваш оригинальный код разрешит неудачный вызов patch, как

declare const item: Item;
patch(item, 7, false); // no error ?

, несмотря на тот факт, что 7 не является действительным ключом свойства Item, а false не являетсядопустимое значение свойства для этого или любого ключа Item.


Давайте рассмотрим ваш конкретный Item интерфейс и посмотрим, что с ним делают keyof и типы поиска .

Оператор типа keyof принимает тип объекта и возвращает тип его ключей.Если у объекта нет индексной подписи , этот тип должен быть объединением литеральных типов , соответствующих его известным действительным ключам:

type KeysOfItem = keyof Item
// type KeysOfItem = "_id" | "name" | "amount" | "type" | "value"

ТакВы хотите, чтобы параметр key был одним из них.Теперь предположим, что у вас есть действительный ключ, например "amount".Затем вы можете найти тип свойства для этого ключа следующим образом:

type ItemAmount = Item["amount"];
// type ItemAmount = number

Так что для любого типа K параметр key необходимо, чтобы параметр value был Item[K].


Итак, первый снимок для вашего типа функции может выглядеть примерно так:

function patch(item: Item, key: keyof Item, value: Item[keyof Item]): Item {
    return {
        ...item,
        [key]: value
    };
}

declare const item: Item;
patch(item, 7, false); // error! 7 is not a key, false is not a value

Это лучше, но:

patch(item, "name", 14); // no error ?

Проблема в том, чточто нет никакой корреляции между конкретным key, который вы использовали, и value, который вы передали.Поскольку "name" - это некоторая клавиша, а number - это некоторое допустимое значение (для "amount"), то это не ошибка.

Способчтобы исправить это, нужно сделать key a generic type K, который может быть более конкретным, чем (или "extended") keyof Item, а затем ограничить value для вводаItem[K]:

function patch<K extends keyof Item>(item: Item, key: K, value: Item[K]): Item {
    return {
        ...item,
        [key]: value
    };
}

declare const item: Item;
patch(item, 7, false); // error! 7 is not a key, false is not a value
patch(item, "name", 14); // error! 14 is not a string
patch(item, "name", "okay"); // works
patch(item, "amount", 14); // okay

Так вот, что я бы предложил.Надеюсь, это поможет;удачи!

2 голосов
/ 08 мая 2019

Поскольку тип значения зависит от переданного ключа, следует использовать параметр общего типа.

  • Мы можем определить Key как расширяющий ключ Item (это означает, что Key можно назначить для типа key of Item)
  • Затем используйте Key для индексации типа элемента для значения (Item [Key]).

Результат:

function patch<Key extends keyof Item>(item: Item, key: Key, value: Item[Key]): Item {
    return {
        ...item,
        [key]: value
    };
}

Вы могли бы пойти еще на один шаг абстракции дальше. Не важно, что эта функция знает тип элемента:

function patch<TItem, TKey extends keyof TItem>(item: TItem, key: Key, value: TItem[Key]): TItem {
    return {
        ...item,
        [key]: value
    };
}

Теперь с любым передаваемым предметом вы можете пропатчить значение

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...