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

时间:2018-02-16 09:04:40

标签: typescript typescript-typings

我试图为下面的场景编写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);
  • x0的类型为ReducerBuilder<{},{}>。我明白在args 这里不能干涉因为compose返回的函数 没有关于论证类型的信息。
  • x1的类型为ReducerBuilder<{},{}>。我不明白这里的类型 是{}。我明确指出TArg是一种State。我怀疑 打字稿试图从左到右干扰TResult1 无法从f2参数中获取它。
  • x2的类型为ReducerBuilder<State,State> - 成功。我所做的只是 只是为了恢复参数的顺序,TResult1可以 干扰从左到右。

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

2 个答案:

答案 0 :(得分:2)

我想说谢谢你这个问题,我被卷入了&#34;这里发生了什么&#34;并意识到我有很多乐趣。我只列出我到目前为止所发现的内容。

首先,你需要向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显示正确的返回类型。 x3x7显示正确的返回类型,因为不需要推断出参数,但我不知道为什么x6可以正常工作。基于x6工作,我会假设x2或除x0x4之外的所有内容都可以使用。

相反,除了正确返回的内容之外,我看到的是一切都返回TResult,除了x4 实际上返回ReducerBuilder<{}, {}>?这是奇怪的行为,因为它只是引起它的参数的顺序。

新的TypeScript 2.8为您提供了ReturnType<T>,这样您就不必在两个函数之间指定类型。但我不知道这是否真的会修复#34;推断问题。

如果您只需要一个类型定义,只需指定返回类型即可。

示例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可能更准确,这样您就可以控制类型是什么,不可能是什么作曲。在下面的代码中,我创建了1个版本的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);

答案 1 :(得分:2)

这里的主要问题涉及TypeScript中的open issue,其中编译器并不真正知道如何在函数本身是通用的高阶函数上进行泛型类型推理。根据{{​​3}}的a comment

  

这是推理从左到右工作的结果,用于上下文类型的参数。在一般情况下解决它需要某种形式的统一,但这可能反过来揭示其他问题......

这符合您的发现,即参数的顺序很重要。

看起来高阶泛型函数的上下文推断可能非常困难(根据对GitHub问题和Anders Hejlsberg的评论)来正确实现。

如果没有这样的实现,您可能需要选择一种解决方法。这些包括@ Camron的linked issues在调用compose()时显式指定类型参数,或等待TypeScript 2.8并使用suggestions来减少所需类型参数的数量 。最简单的解决方法是将参数保留为“反向”顺序。

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

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