Typescript函数参数的泛型约束

6

编辑:在接口中将“test”更正为“constraint”

我对Typescript中的通用约束条件不太理解。我正在使用React/Redux,其中您可以找到类似于以下构造的内容:

interface SomeConstraint { constraint: any }
type SomeType<T> = <A extends SomeConstraint>(item: T, action: A) => void;

正如您所见,这是一个带有通用参数和一些约束条件的函数定义。现在,当我想要使用它时,可以编写以下内容:

interface Test { constraint: string }
const some : SomeType<string> = (s: string, c: Test ) => {}

这很好用,但是当我想要扩展测试接口(让我们称其为TestWithData)时:

interface TestWithData  { constraint: string, payload: any }
const some : SomeType<string> = (s: string, c: TestWithData ) => {}

我遇到了编译时错误:
错误 TS2322:类型“(s: string, c: TestWithData) => void”不能赋值给类型“SomeType”。参数类型“c”和“action”的类型不兼容。类型“A”不能赋值给类型“TestWithData”。类型“SomeConstraint”中缺少属性“test”。
我错过了什么?
编辑:
正如我所说,我在redux类型定义中发现了这个(类似的)结构。您可以在以下定义中找到此定义(redux“3.7.2”):
export type Reducer<S> = <A extends Action>(state: S, action: A) => S;

现在,当您想定义您的reducer时,需要提供state和action,我将跳过state部分,因此,可以将action写成如下形式:

interface MyAction { type: "MY_ACTION" }

创建reducer很容易:

const reducer: Reducer<MyState> = (state: MyState, action: MyAction) => {...}

如果我将额外的数据添加到MyAction中,代码可以通过编译:

interface MyAction { 
    type: "MY_ACTION";
    payload: any
}

如果出现上述错误,编译将失败。我本以为它会通过。那么这个限制的目的是什么呢?是告诉编译器我们期望完全相同的类型(在结构上),没有更多或更少吗?我认为这是一个非常有限的用例,我希望类型推断能够选择类型,检查其结构兼容性并保留类型签名。类似于这样但不需要指定类型参数:

export type Reducer<S, A extends Action> = (state: S, action: A) => S;

现在,类型签名已经被保留,但是在声明变量时需要指定实际参数类型(与状态声明相同):
const reducer: Reducer<MyState, MyAction> = (state: MyState, action: MyAction) => {...}
1个回答

5

对于您所提供的示例,我认为泛型+约束并不是我定义类型的方式(在名义类型系统中可能会采用这种方式,但在结构类型系统中不会这样做)。

type SomeType<T> = <A extends SomeConstraint>(item: T, action: A) => void;

如果您说的是“该项目必须具有constraint属性”,那么您可以使用更简单的方式来获取它:
type SomeType<T> = (item: T, action: SomeConstraint) => void;

结构类型检查处理其余部分。

这是我更新的版本,因为之前的答案在TypeScript 3.8.3中停止工作。我将约束条件设置为通用类型,因为约束条件将与项目相同。由于参数类型可以推断,我们不需要明确声明它们(即我们不需要像这样写三次stringconst actualImplementation: SomeType<string> = (item: string, action: string): void => { } - 我们只需要将其作为类型参数写一次。

interface SomeConstraint<T> {
    constraint: T
}

type SomeType<T> = (item: T, action: SomeConstraint<T>) => void;

const actualImplementation: SomeType<string> = (item, action): void => { }

const a: SomeConstraint<string> = { constraint: '' };

actualImplementation('', a);

上面例子中的关键点是,实际实现可以使用你的Test类型而不会出错。

谢谢你,Fenton。我明白你的意思,但不幸的是我的例子是错的,我不小心在两个接口中都留下了属性“test”的名称,但它应该被命名为“constraint”。至于为什么要这样定义,这超出了我的范围,这实际上是Redux的ts类型定义:export type Reducer<S> = <A extends Action>(state: S, action: A) => S; - negyxo
但除了redux之外,这个结构应该和以下的结构是一样的吧: type SomeType<T, A extends SomeConstraint> = (item: T, action: A) => void? - negyxo
这个答案似乎无法在TypeScript 3.7.5和3.8.3中编译。 - Ulad Kasach
很好的答案!我的问题是为什么扩展不允许添加额外的属性。什么导致它出现故障?它仍然是结构化类型,对吧? - Hassan Naqvi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接