有没有 Typescript 的大佬帮忙看一个泛型接口的问题

2020-03-03 13:35:22 +08:00
 HanMeiM

先上代码

export interface Store {
  [key: string]: any;
}

type OnFinishCallback = (values: Store) => void;

export interface Callbacks {
  onFinish?: OnFinishCallback;
}

export interface TestData {
  name: string;
  password: string;
  age: number;
}

const test1: OnFinishCallback = (values: TestData) => {};

const test2: Callbacks['onFinish'] = (values: TestData) => {};

抛出问题

Typescript 版本:3.7.5

提问:为什么不能匹配泛型接口?单纯好奇,也知道可以通过 <T> 或者中间函数传递来解决。

2988 次点击
所在节点    问与答
25 条回复
johnnyNg
2020-03-03 14:01:45 +08:00
说的是 TestData 和 Store 类型不符合,把 TestData 改成 Store 就好了
mxT52CRuqR6o5
2020-03-03 14:04:18 +08:00
泛型在哪?
HanMeiM
2020-03-03 14:12:46 +08:00
@mxT52CRuqR6o5 害,俺说错了,泛型能匹配上,但是直接这样写 索引签名 不行。
chuxiaonan
2020-03-03 14:18:02 +08:00
export interface TestData extends Store {
...
}
HanMeiM
2020-03-03 14:24:20 +08:00
@chuxiaonan 不行哦,我试过的。
chuxiaonan
2020-03-03 14:51:36 +08:00
@HanMeiM 这样噢 那就不是很了解啦
myl0204
2020-03-03 15:03:32 +08:00
我的理解是实际函数的参数类型是确定的:`TestData `,而 OnFinishCallback 中参数类型其实是不定的,可以是`{a: 1, b:2}`也可以是`{c: 3}`等等。

这二者当然不匹配呀
maichael
2020-03-03 15:26:00 +08:00
因为 Store 类型的值不能赋值给 TestData 类型的变量。你对泛型的理解是有问题的,你想,假设你整个逻辑成立,那你这里的 test1 函数入参就是 Store 类型的变量,但是 Store 类型的变量未必能赋值给 TestData 类型的变量,所以逻辑上不通。反过来才是对的,但意图就可能跟你原有的不一致了,你可以说下你的需求是啥吗

type OnFinishCallback = (values: TestData) => void;

const test1: OnFinishCallback = (values: Store) => {};
geelaw
2020-03-03 15:41:12 +08:00
Store 是水果,TestData 是橘子,OnFinishCallback 是“能处理任意水果”的类型。因为 test1 初始化的表达式不能处理任意水果(只能处理橘子),所以不行。

用术语来说是 A => void 对 A 是逆变,如果 B is-an A,则 (A => void) is-a (B => void),而不是反过来。
HanMeiM
2020-03-03 17:02:05 +08:00
@maichael
@geelaw
其实也不是我写成这样,我肯定还是希望用泛型去解决。
我还是直接说我在哪遇到的问题吧。
我今天更新 Ant-Design 4.0.0 的时候,Form 组件有很大的改变。
官方推荐使用 `Form` 组件的 `onFinish` 属性来获取表单值。
下面是这个属性的定义。

![image.png]( https://i.loli.net/2020/03/03/fGa5TFlbCqAWZtJ.png)

![image.png]( https://i.loli.net/2020/03/03/JOx3lFEbDmCwWqA.png)

![image.png]( https://i.loli.net/2020/03/03/Um3qOG2BRAMy4xt.png)

然后上图的 `test2` 就是我写的获取表单的方法,很明显会报错,所以有点头疼。
目前是直接在 `Form` 表单中这样写来解决

```Typescript
onFinish={(value: Store) => test2(value)}
```
maichael
2020-03-03 17:29:31 +08:00
@HanMeiM #10 暂时没有,这个 Antd 这个东西的问题。

https://github.com/ant-design/ant-design/issues/21195

https://github.com/react-component/field-form/issues/70

简单来说,他这里本应该提供 "Generic type arguments" 来自定义 StoreValue 的类型,但他没有给,所以你从外部没法去改变他内部的类型。你现在只能强转类型然后等他修好。
HanMeiM
2020-03-03 17:37:44 +08:00
@maichael 恩呢,感谢
buffzty
2020-03-03 20:54:20 +08:00
TestData 类型是兼容 Store 的,但是 (v: TestData )=>{} 这个函数类型 是不兼容 (v: Store)=>{} 这个函数类型的
可能是 ts 为了检测简单,不然还要把所有函数的参数再检测一遍.

我的建议:

去掉函数形参中的类型,此时 ts 会自动推断为 Store
传参时可以传 TestData 类型.
以下代码在 3.7.5 无报错
```typescript
export interface Store {
title: string // 加这个只是为了测试自动推断
[key: string]: any
}
type OnFinishCallback = (values: Store) => void
export interface Callbacks {
onFinish?: OnFinishCallback
}
export type TestData = Store & {
name: string
password: string
age: number
}
let a: TestData = { b: 123, age: 12, name: '1', password: '', title: '' }
let b: Store = a
const test1: OnFinishCallback = values => {
console.log(values.title) // 这里的 values 已经被自动推断过了
}

const test2: Callbacks['onFinish'] = values => {
console.log(values.title)
}
test1(a)
test1(b)
test2(a)
test2(b)

```
Mistwave
2020-03-03 21:11:09 +08:00
对于函数类型,入参是逆变的,而返回值是协变的。
也就是说,参数类型可以用其超类型替代,返回类型可以用子类型替代,而不能反过来。
例子可以参考 @geelaw 的回复
HanMeiM
2020-03-03 21:44:58 +08:00
@Mistwave
@geelaw
学习了学习了
HanMeiM
2020-03-03 21:46:00 +08:00
@buffzty 你这个方法已经放弃了 Typescript 的优势了
xiaojie668329
2020-03-03 21:49:42 +08:00
TestData 的类型显然更具体了,它的类型被收窄(narrow down)了,test1 中的 values 的限制更多,不匹配 OnFinishCallback 中的 Store。也就是,OnFinishCallback 的 values 参数能够接受任意的对象,而你给 test1 的 values 定义的参数中只能包含 name, password, age 这三个属性。
不需要给 test1 中的 values 定义类型 TestData,它会被推断为 Store。
buffzty
2020-03-03 22:44:18 +08:00
@HanMeiM 为什么去掉形参类型名叫放弃优势呢? 去掉之后什么代码也没多写,类型也自动推断了. 你不就是要这个效果吗 本来能自动推断的,你非要给他加个错误类型
HanMeiM
2020-03-03 23:22:31 +08:00
@buffzty 很明显这个 Store 应该是个泛型,只不过他这里写的有问题。你实际写代码的时候,看你代码的人能通过 Store 知道什么呢?我能知道这个表单有哪些数据?这个数据是什么类型?我还是得看上下文代码。
maichael
2020-03-04 09:16:12 +08:00
@HanMeiM #12

const test1: OnFinishCallback = ((values: TestData) => {}) as OnFinishCallback

一个勉强用着的解决方法。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/649397

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX