你如何输入一个功能性的mixin reducer?

How can you type a functional mixin reducer?

我正在学习打字稿,并且正在努力弄清楚如何正确地键入减少函数混合的缩减器函数。

给定两个功能混合,如:

type FooComposable = {
  foo: string;
};
const withFoo = (composable): FooComposable => {
  composable.foo  = 'foo';

  return composable;
};

type BarComposable = {
  bar: string;
};
const withBar = (composable): BarComposable => {
  composable.bar = 'bar';

  return composable;
};

我有一个 reducer 函数,可以减少所有提供的功能混合:

const reduce(...fns) = fns.reduce((acc, fn) => fn(acc), {}));

reduce(withFoo); // -> { foo: 'foo' }
reduce(withBar); // -> { bar: 'bar' }
reduce(withFoo, withBar); // -> { foo: 'foo', bar: 'bar' }

如何将类型添加到 reduce() 函数(和函数混合),以便生成的简化可组合项具有预期的类型推断?

type FooComposable = {
  foo: string;
};
const withFoo = <T extends FooComposable>(composable: T): FooComposable => {
  composable.foo = 'foo';

  return composable;
};

type BarComposable = {
  bar: string;
};
const withBar = <T extends BarComposable>(composable: T): BarComposable => {
  composable.bar = 'bar';

  return composable;
};

type FunctionalMixin<T extends {}> = (composable: T) => T;
const reduce = <T extends {}>(...fns: FunctionalMixin<T>[]): T =>
  fns.reduce((acc, fn) => fn(acc), {});
/* Type '{}' is not assignable to type 'T'.
  '{}' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.  */

reduce<FooComposable & BarComposable>(withFoo, withBar); // -> { foo: 'foo', bar: 'bar' }
/* Argument of type '<T extends FooComposable>(composable: T) => FooComposable' is not assignable to parameter of type 'FunctionalMixin<FooComposable & BarComposable>'.
  Type 'FooComposable' is not assignable to type 'FooComposable & BarComposable'.
    Property 'bar' is missing in type 'FooComposable' but required in type 'BarComposable'. */


How can I add typings to the reduce() function (and functional mixins) such that a resultant reduced composable has expected type inferences?

在以下示例中,result 具有正确推断的类型 Foo & Bar

Playground

type Foo = {
    foo: string;
};

const withFoo: Mixin<Foo> = (a) => ({ ...a, foo: 'foo' });

type Bar = {
    bar: string;
};

const withBar: Mixin<Bar> = (a) => ({ ...a, bar: 'bar' });

type Mixin<B> = <A extends object>(a: A) => A & B;

type Reduce<A extends object, T extends unknown[]> =
    T extends [] ? A :
    T extends [Mixin<infer B>, ...infer C] ? Reduce<A & B, C> :
    never;

const reduce = <T extends Mixin<unknown>[]>(...fns: T): Reduce<{}, T> =>
    fns.reduce((a, fn) => fn(a), {}) as Reduce<{}, T>;

const result = reduce(withFoo, withBar);