Типы сохраняемых типов в Typescript Generics

#typescript #generics

#typescript #универсальные программы

Вопрос:

У меня есть два типа, определенные как:

 type Foo<N extends string, T extends any> = { [name in N]: T };
type Bar<N extends string, T extends any> = { name: N; obj: T };
  

И у меня есть функция, определенная как:

 function makeFooFromBar<N extends string, T extends any>(params: Array<Bar<N, T>>): Foo<N, T> {
    const ret: any = {};

    for (const { name, obj } of params) {
        ret[name] = obj;
    }

    return ret;
}
  

Что это должно делать, так это:

 const foo = makeFooFromBar([
    { name: 'abc', obj: 10 },
    { name: 'def', obj: 20 },
]);

// has value 10
foo.abc;

// has value 20
foo.def;
  

Пока это работает нормально, но это работает только тогда, obj когда всегда один и тот же тип. Например, это не работает:

 const foo = makeFooFromBar([
    { name: 'abc', obj: 10 },
    { name: 'def', obj: 20 },
    { name: 'xyz', obj: 'abc' }, // error, string can not be assigned to number
]);
  

Есть ли способ заставить это работать? Также мне нужно, чтобы исходные типы были сохранены, поэтому foo.abc и foo.def должны быть number и foo.xyz должны быть string (нехорошо, когда все три из них являются number | string ).).

Комментарии:

1. Что такое типы Def и Ref?

2. Извините, я забыл настроить их для типов Foo и Bar. Я обновлю вопрос.

Ответ №1:

Вы можете заставить его компилироваться, явно указав общие типы:

 const foo2 = makeFooFromBar<'abc' | 'def' | 'xyz', 10 | 20 | 'abc'>([
    { name: 'abc', obj: 10 },
    { name: 'def', obj: 20 },
    { name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<"abc" | "def" | "xyz", "abc" | 10 | 20>
  

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

Но это очень раздражает в использовании.


Другим вариантом является явный общий ввод всего массива, чтобы TypeScript не выводил типы слишком рано:

 function makeFooFromBar<N extends string, T, A extends Array<Bar<N, T>>>(params: A): Foo<N, T> {
    ...
}

const foo2 = makeFooFromBar([
    { name: 'abc', obj: 10 },
    { name: 'def', obj: 20 },
    { name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<string, unknown>
  

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

Но вы можете видеть, что по какой-то причине это портит тип T .


Мы можем перенести вывод на возвращаемый тип, что, кажется, немного помогает:

 function makeFooFromBar<A extends Array<Bar<any, any>>>(params: A): A extends Array<Bar<infer N, infer T>> ? Foo<N, T> : never {
    ...
}

const foo2 = makeFooFromBar([
    { name: 'abc', obj: 10 },
    { name: 'def', obj: 20 },
    { name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<string, string | number>
  

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


Также возможно сделать это с очень причудливыми сложными типами, чтобы получить каждый ключ и значение в виде типа, но не так уверен в этом… Ссылка на игровую площадку