打字稿通用和扩展

时间:2020-01-28 07:57:06

标签: typescript typescript2.0 typescript-generics typescript1.5

我是Typescript的新手。

我有以下4个界面:

export interface MyAction<T = any> {
  type: T
}

export interface MyAnyAction extends MyAction {
  [extraProps: string]: any
}

export interface MyAnyAnyAction extends MyAnyAction{

}

export interface ITestDispatch<A extends MyAction = MyAnyAction> {
  <T extends A>(action: T): T
}

我想创建一个类型为“ ITestDispatch”的函数。

我不明白为什么TS编译器会为以下功能抛出错误:

const TestDispatch1Func: ITestDispatch1<MyAnyAction> = (action: MyAnyAnyAction): MyAnyAnyAction => {


  let obj: MyAnyAnyAction = { 
        type: 'skdw',
        da: 20
  };

  return obj;
}

我在“ TestDispatch1Func”上遇到以下错误:

Type '(action: MyAnyAnyAction) => MyAnyAnyAction' is not assignable to type 'ITestDispatch<MyAnyAction>'.   Type 'MyAnyAnyAction' is not assignable to type 'T'.     'MyAnyAnyAction' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyAnyAction'

感谢清除我的疑问。

2 个答案:

答案 0 :(得分:2)

这实际上是函数签名中的一个模糊错误。 <MyAnyAction>声明了一个新的Type Parameter,它更改了MyAnyAction的含义。由于类型参数与代码中的接口同名,因此很难发现。

const TestDispatch1Func: ITestDispatch<MyAnyAction> =
    <MyAnyAction>(action: MyAnyAction): MyAnyAction => {

应该是

const TestDispatch1Func: ITestDispatch<MyAnyAction> =
    (action: MyAnyAction): MyAnyAction => {

对于更多上下文,您可以将<MyAnyAction>重命名为任何内容,因为它是类型参数,在此上下文中,这意味着创建一个名为MyAnyAction的类型参数。如果重命名为,则错误也更加明显:

const TestDispatch1Func: ITestDispatch<MyAnyAction> = <T>(action: T): T => {

类型“ MyAnyAction”不可分配给类型“ T”。 “ MyAnyAction”为 可分配给类型“ T”的约束,但“ T”可以是 用约束'{}'

的其他子类型实例化

答案 1 :(得分:1)

这是因为接口ITestDispatch意味着该函数可以执行A的任何子类型的操作,因此它与函数TestDispatch1Func的类型声明和返回类型冲突,后者仅限于MyAnyAnyAction。该接口接受A的任何子类型,而实现只接受1个子类型。

例如,如果您有另一个界面

export interface AnotherAction extends MyAnyAction{}

ITestDispatch1<MyAnyAction>起,定义TestDispatch1Func(action: AnotherAction)允许您调用AnotherAction extends MyAnyAction,但这显然与仅期望MyAnyAnyAction的函数定义冲突。

这里有3种解决方案

export interface MyAction<T = any> {
  type: T
}

export interface MyAnyAction extends MyAction {
  [extraProps: string]: any
}

export interface MyAnyAnyAction extends MyAnyAction{}


// solution 1: specify the action input & output types in function defnition
export interface ITestDispatch1<T extends MyAction = MyAnyAction> {
  (action: T): T
}

// this means that the function will always be called with MyAnyAnyAction and return MyAnyAnyAction
const TestDispatchFunc1: ITestDispatch1<MyAnyAnyAction> = (action) => {
  // here you can always return `MyAnyAnyAction`,
  // because you explicitly declared it the as function output type
  let obj: MyAnyAnyAction = { 
        type: 'skdw',
        da: 20
  };

  return obj;
}


// solution 2: separate action input & output types, specify output type in function defintion
export interface ITestDispatch2<A extends MyAction, R extends A> {
  <T extends A>(action: T): R
}

// this function can be called with any subtype of MyAnyAction, but will always return MyAnyAnyAction
const TestDispatchFunc2: ITestDispatch2<MyAnyAction, MyAnyAnyAction> = (action) => {
  // here you can always return `MyAnyAnyAction`,
  // because you explicitly declared it the as function output type
  let obj: MyAnyAnyAction = { 
        type: 'skdw',
        da: 20
  };

  return action;
}


// solution 3: decide the function input & output types types in function invocation
export interface ITestDispatch3<A extends MyAction = MyAnyAction> {
  <T extends A>(action: T): T
}

// this function can be called with any subtype of MyAnyAction, returns the same subtype
const TestDispatchFunc3: ITestDispatch3<MyAnyAction> = (action) => {
  // can't return MyAnyAnyAction because the return type is the same as the input type,
  // which can be any subtype of MyAnyAction, not necessarily MyAnyAnyAction
  // let obj: MyAnyAnyAction = { 
  //       type: 'skdw',
  //       da: 30
  // };

  return action;
}

// the result type is determined base on the input type
const result1 = TestDispatchFunc3({} as MyAnyAnyAction) // result: MyAnyAnyAction
const result2 = TestDispatchFunc3({} as MyAnyAction) // result: MyAnyAction

TS Playground here