打字稿通用和扩展

Posted

技术标签:

【中文标题】打字稿通用和扩展【英文标题】:Typescript generic and extend 【发布时间】:2020-05-13 14:34:30 【问题描述】:

我是 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) =&gt; MyAnyAnyAction' is not assignable to type 'ITestDispatch&lt;MyAnyAction&gt;'.   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'.

感谢您解决我的疑问。

【问题讨论】:

&lt;T extends A&gt;(action: T): T 意味着您的函数必须返回与其参数相同的 A 子类型。所以你的函数接受一个参数action,它可能是MyAnyAction的子类型,并且必须返回相同的子类型;但它实际上只返回基本类型MyAnyAction @kaya3 我已经修改了我的问题。请您看一下并告诉我修改后的示例是否满足您的论点的要求?谢谢。 你可以说它没有,因为 Typescript 仍然会给你类型错误。函数类型&lt;T extends A&gt;(action: T): T 仅由返回与其参数相同类型的函数满足,该类型可以是A 的任何子类型。您修改后的函数完全忽略了其参数action 的类型,并且只返回MyAnyAction 的一个硬编码子类型,因此它不能保证返回与action 相同的子类型。 【参考方案1】:

这实际上是您的函数签名中的一个模糊错误。 &lt;MyAnyAction&gt; 声明了一个新的类型参数,它改变了 MyAnyAction 的含义。很难发现,因为类型参数与代码中的接口同名。

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

应该是

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

对于更多上下文,您可以将&lt;MyAnyAction&gt; 重命名为任何内容,因为它是一个类型参数,在此上下文中,它意味着创建一个名为 MyAnyAction 的类型参数。如果你重命名是那么错误也更清楚:

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

类型“MyAnyAction”不可分配给类型“T”。 'MyAnyAction' 是 可分配给“T”类型的约束,但“T”可以是 使用不同子类型的约束“”进行实例化

【讨论】:

谢谢。但是,如果我使用上述构造,那么我会收到以下错误:类型“(操作:MyAnyAction)=> MyAnyAction”不可分配给类型“ITestDispatch1”。类型“MyAnyAction”不可分配给类型“T”。 “MyAnyAction”可分配给“T”类型的约束,但“T”可以用约束“MyAnyAction”的不同子类型来实例化 @sudip 我认为问题是为什么你希望ITestDispatch 成为T 中的通用函数。如果您真的想让它通用,这将起作用:typescriptlang.org/play/#code/…。但是你不能创建 T 类型的对象文字,因为 T 可以是任何东西 @sudip 上述构造的要点是您会收到一个错误,但消息会显示您的上下文中的 MyAnyAction 是一个类型参数。【参考方案2】:

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

例如,如果你有另一个接口

export interface AnotherAction extends MyAnyAction

ITestDispatch1&lt;MyAnyAction&gt; 的定义允许您调用 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

【讨论】:

以上是关于打字稿通用和扩展的主要内容,如果未能解决你的问题,请参考以下文章

打字稿:使用特定的扩展接口来满足通用的“扩展”类型参数?

通用无状态组件 React 的类型?或在打字稿中扩展通用函数接口以具有进一步的通用性?

打字稿通用服务

打字稿泛型类参数

具有通用方法和继承的打字稿工厂,错误不可分配给类型

使用静态方法的打字稿继承