TypeScript: аргумент универсального типа функции выводится из неправильного параметра

В приведенном ниже коде, если я не укажу T явно при вызове функции, например getOrPut<Item>(...), это выводится из параметра create, в результате созданный тип элемента может быть несовместим со словарем obj, см. последнюю строку кода для пример.

function getOrPut<T>(
    obj: { [key: string]: T | undefined },
    key: string,
    create: () => T
): T {
    const value = obj[key];
    if (value) {
        return value;
    } else {
        return obj[key] = create();
    }
};

type Item = { title: string };
type Dictionary = { [key: string]: Item };
const dictionary: Dictionary = {};

// the foo type is {} but I expect Item
const foo = getOrPut(dictionary, 'foo', () => ({}));

Можно ли заставить T выводиться из параметра obj?

Playground link.


person Valeriy Katkov    schedule 19.11.2019    source источник
comment
@griFlo Поскольку getOrPut это служебная функция, я хочу максимально упростить ее использование. В некоторых случаях тип элемента может быть сложным, например { [key: string]: { title: string } } или { [key: string]: Omit<Item, 'foo'> }. Неудобно указывать такой сложный тип при каждом вызове функции.   -  person Valeriy Katkov    schedule 19.11.2019


Ответы (2)


Это работает, вы должны передать Item в аргументе create.

function getOrPut<T>(
    obj: { [key: string]: T | undefined },
    key: string,
    create: () => T
): T {
    const value = obj[key];
    if (value) {
        return value;
    } else {
        return obj[key] = create();
    }
};

type Item = { title: string };
type Dictionary = { [key: string]: Item }
const dictionary: Dictionary = {};

// the foo type is {} but I expect Item{
const foo = getOrPut(dictionary, 'foo', () => ({} as Item)); // <--- casting here

Ссылка на игровую площадку

person Alessandro Cifani    schedule 19.11.2019
comment
Спасибо, но я предпочитаю решение без приведения типов. На самом деле я только что нашел обходной путь, я добавлю свой ответ через несколько минут. - person Valeriy Katkov; 19.11.2019
comment
конечно, это был просто пример, решение будет сильно зависеть от самого типа и того, как вы хотите с ним обращаться ???? - person Alessandro Cifani; 19.11.2019

Я нашел обходной путь, указав тип возврата create с точки зрения типа obj:

function getOrPut<T>(
    obj: { [key: string]: T | undefined },
    key: string,
    create: () => NonNullable<typeof obj[string]>
): T { ... }

К сожалению, по какой-то причине решение работает только с TypeScript 3.6.3, в то время как моя текущая версия 3.5.3, но она должна быть обновлена ​​в ближайшее время. Я не уверен, что это лучшее решение, может быть, есть лучшее.

Playgound ссылка.

person Valeriy Katkov    schedule 19.11.2019