【问题标题】:Why does this Typescript generic type inference fail?为什么这个 Typescript 泛型类型推断会失败?
【发布时间】:2019-09-11 09:50:54
【问题描述】:

我有 2 个版本的数据模型(称为 AB),我希望能够在这些实例上执行函数,无论它们是 A 还是 B。函数本身特定于A 型和 B 型。

我想出了解决此问题的方法,但它们都涉及声明一个泛型类型(即Functions<T>),而我目前的设置无法轻松做到这一点。

type A = {
  a: string;
}

type B = {
  b: string;
}

type AFunctions = {
  make: () => A;
  do: (obj: A) => void;
}

type BFunctions = {
  make: () => B;
  do: (obj: B) => void;
}

type Bundle<
  Fns extends AFunctions | BFunctions,
  T extends ReturnType<Fns['make']> = ReturnType<Fns['make']>
> = {
  obj: T,
  fns: Fns,
}

function doIt<M extends AFunctions | BFunctions>(bundle: Bundle<M>) {
  bundle.fns.do(bundle.obj);
}

Typescript playground

bundle.fns.do(bundle.obj); 行,我得到一个打字稿错误:Argument of type 'ReturnType&lt;M["make"]&gt;' is not assignable to parameter of type 'A &amp; B'

我希望doIt 是类型安全的,因为bundle.objbundle.fns.do 上的参数类型相同。怎么了,这里?有没有办法在不引入通用Functions&lt;T&gt; 的情况下解决这个问题?


我也可以将{ type: 'a' }{ type: 'b' }参数分别添加到Bundles,然后检查:

if (bundle.type === 'a') {
  bundle.fns.do(bundle.obj);
} else if (bundle.type === 'b') {
  bundle.fns.do(bundle.obj);
}

但这种冗余并不理想。

我认为这与缩小泛型类型的问题有关:https://github.com/microsoft/TypeScript/issues/17859

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    我认为编译器的抱怨是对的。考虑以下几点:

    let
    func : AFunctions | BFunctions = {
      'make' : function() : A { return {'a': "A"} },
      'do' : function(_ : A) { }
    },
    someB : B = { 'b' : "B" },
    bundle : Bundle<AFunctions | BFunctions> = {
      'obj' : someB,
      'fns' : func,
    }
    

    此类型检查,但 'do' 显然不能使用参数 'obj' 调用。根本问题是,在bundle 中,T 类型被推断为A | B,而不是AB,基于make 的类型,正如我认为您所期望的那样。

    目前还不清楚您想要实现什么。特别是,不清楚为什么不能声明泛型类型,因为这似乎正是您所需要的:

    type GenericBundle<X> = { obj : X, do : (obj : X) => void };
    type AFunctions = GenericBundle<A>;
    type BFunctions = GenericBundle<B>;
    type Bundle = AFunctions | BFunctions;
    
    function doIt<X>(bundle: GenericBundle<X>) {
      bundle.do(bundle.obj);
    }
    
    let
    someA : A = { 'a' : "A" },
    someB : B = { 'b' : "B" },
    bundle1 : Bundle = {
      'obj' : someA,
      'do' : function(_ : A) { },
    },
    bundle2 : Bundle = {
      'obj' : someB,
      'do' : function(_ : B) { },
    },
    bundle3_wrong : Bundle = { // doesn't typecheck
      'obj' : someA,
      'do' : function(_ : B) { },
    },
    bundle4_wrong : Bundle = { // doesn't typecheck
      'obj' : someB,
      'do' : function(_ : A) { },
    };
    
    doIt(bundle1);
    doIt(bundle2);
    
    

    【讨论】:

      【解决方案2】:

      不确定这是否会满足您的用例,但也许会有所帮助 - playground link

      这里的缺点是 do::obj 参数上的 'any'(可以是 'T | any' 或只是 'any'),这可能对你来说很重要,也可能无关紧要。

      【讨论】:

      • 嗨,Alex,很高兴您加入社区。虽然发布示例链接很棒,但最好在您的答案中实际发布您的代码。现在,上下文丢失了。见:stackoverflow.com/help/how-to-answer
      • 是的,我需要避免 any - 因为在这种情况下,如果我们添加 any,我们可以为任何类型的 x 调用 bundle.fns.do(x) 而不会抱怨打字稿。
      猜你喜欢
      • 2021-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 1970-01-01
      • 2019-09-17
      • 2011-03-19
      • 2017-06-22
      相关资源
      最近更新 更多