#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>
Также возможно сделать это с очень причудливыми сложными типами, чтобы получить каждый ключ и значение в виде типа, но не так уверен в этом… Ссылка на игровую площадку