为什么此Typescript泛型类型推断失败?

时间:2019-09-10 21:11:30

标签: typescript typescript-generics

我有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<M["make"]>' is not assignable to parameter of type 'A & B'

我希望doIt是类型安全的,因为bundle.objbundle.fns.do上的参数类型相同。怎么了,这里?有没有解决方法,而无需引入通用的Functions<T>


我还可以通过将{ type: 'a' }{ type: 'b' }参数分别添加到Bundle中,然后进行检查来解决此问题:

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

2 个答案:

答案 0 :(得分:2)

我认为编译器可以抱怨。请考虑以下内容:

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 | BA,而不是Bmake ,正如我想的那样。

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

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);

答案 1 :(得分:0)

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

这里的缺点是do :: obj参数上的“ any”(可以是“ T | any”,也可以只是“ any”),对您而言可能无关紧要。