Вывод типа TypeScript из массива, переданного как параметр функции - PullRequest
0 голосов
/ 02 апреля 2020

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

interface A {
    functions: { t: number, a: { test: number }; };
}

interface B {
    functions: { t: number, b: { ciao: number }; };
}

function combineStuff<TNamespace extends {}>(stuff: { functions: Partial<TNamespace> }[]): TNamespace {
    // Implementation irrelevant for the question
    return {functions: merge({}, ...stuff.map(s => s.functions))} as any;
}
const a: A = { functions: { t: 1, a: { test: 1 } } };
const b: B = {functions: { t: 1, b: { ciao: 2 } }};
const c = combineStuff([a, b]);
c.a.test;
c.b.ciao;  // Error. Why is that?

const d = combineStuff<A['functions'] & B['functions']>([a, b]);
d.a.test;
d.b.ciao; // Works well

Я ожидал, что и c.b.ciao, и d.b.ciao были безопасны по типу, но доступ к c.b.ciao вызывает ошибку.

Из этого простого примера видно, что тип generi c может быть автоматически выведен из элементов этого массива, эта гипотеза также подтверждается тем фактом, что в вызове combineStuff<A['functions'] & B['functions']>([a, b]); компилятор TS проверяет, что типы a и b действительно правильные.

Кроме того, вывод типа работает правильно для первого элемента массива a я могу безопасно ввести тип c.a.test.

Почему мне нужно явно указать тип generi c для combineStuff(), чтобы получить правильный результат ? Почему вывод первого элемента массива работает правильно?

1 Ответ

2 голосов
/ 02 апреля 2020

Проблема в том, что, поскольку вы используете Partial в определении массива, TS просто выберет один из типов (первый, с которым он столкнется), а затем просто проверит, что другой тип является назначаемым, что, поскольку мы говорим о Partial это будет.

Решением будет использование параметра типа для всего объекта. Это захватит объединение A и B. Затем мы можем использовать запрос типа индекса (T['functions']), чтобы получить объединение типов function. И используйте UnionToIntersection shoutout jcalz , чтобы преобразовать его в желаемое пересечение.

interface A {
    functions: { t: number, a: { test: number }; };
}

interface B {
    functions: { t: number, b: { ciao: number }; };
}
type UnionToIntersection<T> =
  (T extends T ? (p: T) => void : never) extends (p: infer U) => void ? U : never;
function combineStuff<TNamespace extends Array<{ functions: object }>>(stuff: TNamespace):  UnionToIntersection<TNamespace[number]['functions']>{
    // Implementation irrelevant for the question
    return null! as any;
}
const a: A = { functions: { t: 1, a: { test: 1 } } };
const b: B = { functions: { t: 1, b: { ciao: 2 } } };

const c = combineStuff([b, a]);

c.a.test;
c.b.ciao; 

Playground Link

...