打字稿中通用参数的顺序导致参数被干扰为{}

Order of generic parameters in typescript causes the parameter to be interfered as {}

我正在尝试为以下场景编写 compose 函数的类型。

  interface ReducerBuilder<InS, OutS> {

    }
    interface State {
      hue2: number,
      hue3: string,
      hue: string;
    }

    declare function createBaseReducer <K>(initialState: K): ReducerBuilder<K, K> ;
    declare function createReducerTestI <K>(builder: ReducerBuilder<K, K>): ReducerBuilder<K, K>;

    declare function compose<TArg,TResult, TResult1>(f2: (arg: TResult1) => TResult, f1: (arg: TArg) => TResult1): (arg: TArg) => TResult;
    declare function composeRev<TResult, TArg, TResult1>(f1: (arg: TArg) => TResult1, f2: (arg: TResult1) => TResult): (arg: TArg) => TResult;

    const state : State = { hue2: 5, hue3: "aa", hue: "aa" };
    const built = createReducerTestI(createBaseReducer(state));

    const x0 = compose(createReducerTestI, createBaseReducer)(state);
    const x1 = compose(createReducerTestI, (arg: State) => createBaseReducer(arg))(state); 
    const x2 = composeRev((arg: State) => createBaseReducer(arg), createReducerTestI)(state);

我真的不想反转参数的顺序。有没有更好的方法来解决这个问题?

我想说谢谢你提出这个问题,我陷入了 "what is going on here" 并意识到我玩得很开心。我将列出到目前为止我发现的内容。

首先,您需要向 ReducerBuilder<InS, OutS> 添加一些内容,类型被推断为 {},这导致了其他问题,因为 TArg 也被推断为 {}。据我所知,问题的根源不是参数的顺序。这是推断类型的失败。我实际上不确定它应该如何推断它们,所以我不会去猜测正在发生的事情,但是通过各种调用方式进行枚举会更有启发性:

const x0 = compose(createReducerTestI, createBaseReducer)(state);
const x1 = compose(createReducerTestI, (arg: State) => createBaseReducer(arg))(state);
const x2 = compose((arg: ReducerBuilder<State, State>) => createReducerTestI(arg), createBaseReducer)(state);
const x3 = compose((arg: ReducerBuilder<State, State>) => createReducerTestI(arg), (arg: State) => createBaseReducer(arg))(state);

const x4 = composeRev(createBaseReducer, createReducerTestI)(state);
const x5 = composeRev(createBaseReducer, (arg: ReducerBuilder<State, State>) => createReducerTestI(arg))(state);
const x6 = composeRev((arg: State) => createBaseReducer(arg), createReducerTestI)(state);
const x7 = composeRev((arg: State) => createBaseReducer(arg), (arg: ReducerBuilder<State, State>) => createReducerTestI(arg))(state);

只有 x3x6x7 显示正确的 return 类型。 x3x7 显示正确的 return 类型,因为不需要推断参数,但我完全不知道为什么 x6 有效。基于 x6 工作,我假设 x2 或除 x0x4 之外的所有内容都可以工作。

相反,除了return正确的那些,我看到的都是returns TResult,除了x4 实际上[=55] =] return正在ReducerBuilder<{}, {}>?这是一种奇怪的行为,因为它只是导致它的参数顺序。

新的 TypeScript 2.8 为您提供 ReturnType<T>,这将使您不必在两个函数之间指定类型。但我不知道这是否真的会 "fix" 推断问题。

如果您只需要类型定义,只需指定 return 类型即可。

示例 1:

const composedA = compose<State, ReducerBuilder<State, State>, ReducerBuilder<State, State>>(createReducerTestI, createBaseReducer);
const x0 = composedA(state);

或者,正如您已经用 built 变量指出的那样:

示例 2:

const composedB = (arg: State) => createReducer(createBaseReducer(arg));
const x0 = composedB(state);

但这在描述文件中并不是很有用。我觉得组合函数的方式就像示例 2 中那样。但是,创建重载版本的组合可能会更准确,这样您就可以通过类型来控制什么是组合,什么是不可能组合.在下面的代码中,我创建了你的 compose 的一个版本,以及另一个可笑的版本来展示你将如何做到这一点。这也意味着您可以减少所需的类型,以便明确说明可以组合的函数。

函数重载:

interface State1 {
    hue2: number;
    hue3: string;
    hue: string;
}

interface State2 {
    hue4: number;
    hue3: string;
    hue: string;
}

interface ReducerBuilder<InS, OutS> { in: InS; out: OutS; }
interface RediculousObject<InS, OutS> { a: InS; b: OutS; }

type TA_1<T> = ReducerBuilder<T, T>;
type CTA_1<T> = (a: T) => TA_1<T>;
type CTB_1<T> = (a: TA_1<T>) => TA_1<T>;

type TA_2<T, Y> = RediculousObject<T, Y>;
type CTA_2<T, Y> = (a: T) => TA_2<T, Y>;
type CTB_2<T, Y> = (b: Y) => CTA_2<T, Y>;
type CTC_2<T, Y> = (a: T, b: Y) => TA_2<T, Y>;

declare function _compose <T>(a: CTB_1<T>, b: CTA_1<T> | CTB_1<T>): CTA_1<T>;
declare function _compose <T, Y>(a: CTB_2<T, Y>, b: CTA_2<T, Y> | CTB_2<T, Y>): CTC_2<T, Y>;

const state1: State1 = { hue2: 5, hue3: "aa", hue: "aa" };
const state2: State2 = { hue4: 5, hue3: "aa", hue: "aa" };

declare function createBaseReducer <K>(initialState: K): ReducerBuilder<K, K>;
declare function createReducerTestI <K>(builder: ReducerBuilder<K, K>): ReducerBuilder<K, K>;
const f0 = _compose<State1>(createReducerTestI, createBaseReducer)(state1);

declare function _rediculous(a: State2): (b: State1) => RediculousObject<State1, State2>;
const f1 = _compose<State1, State2>(_rediculous, _rediculous)(state1, state2);

这里的主要问题涉及一个open issue in TypeScript where the compiler doesn't really know how to do generic type inference over higher-order functions where the functions are themselves generic. According to a comment by Anders Hejlsberg,

This is a consequence of inference working left-to-right for contextually typed arguments. To solve it in the general case would require some form of unification, but that might in turn uncover other issues...

这与您发现的参数顺序很重要一致。

看起来高阶泛型函数的上下文推断可能很难正确实施(根据对 GitHub 问题和 linked issues 的评论)。

如果没有这样的实现,您可能需要选择一个解决方法。这些包括@Camron 的 to specify type parameters explicitly when calling compose(), or wait for TypeScript 2.8 and use conditional types 以减少您需要的类型参数的数量 。最简单的解决方法是将参数保留在 "reverse" 顺序中。

希望这至少有一些用处。祝你好运!

编辑:我尝试使用 ReturnType<>ArgumentType<> 之类的条件类型,但它仍然不起作用。 higher-order 函数的泛型类型参数变为 {} 的问题仍然存在。