V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
HanMeiM
V2EX  ›  问与答

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

  •  
  •   HanMeiM · 2020-03-03 13:35:22 +08:00 · 2971 次点击
    这是一个创建于 1726 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先上代码

    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) => {};
    

    抛出问题

    image.png

    Typescript 版本:3.7.5

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

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

    这二者当然不匹配呀
    maichael
        8
    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
        9
    geelaw  
       2020-03-03 15:41:12 +08:00   ❤️ 3
    Store 是水果,TestData 是橘子,OnFinishCallback 是“能处理任意水果”的类型。因为 test1 初始化的表达式不能处理任意水果(只能处理橘子),所以不行。

    用术语来说是 A => void 对 A 是逆变,如果 B is-an A,则 (A => void) is-a (B => void),而不是反过来。
    HanMeiM
        10
    HanMeiM  
    OP
       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
        11
    maichael  
       2020-03-03 17:29:31 +08:00   ❤️ 1
    @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
        12
    HanMeiM  
    OP
       2020-03-03 17:37:44 +08:00
    @maichael 恩呢,感谢
    buffzty
        13
    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
        14
    Mistwave  
       2020-03-03 21:11:09 +08:00
    对于函数类型,入参是逆变的,而返回值是协变的。
    也就是说,参数类型可以用其超类型替代,返回类型可以用子类型替代,而不能反过来。
    例子可以参考 @geelaw 的回复
    HanMeiM
        15
    HanMeiM  
    OP
       2020-03-03 21:44:58 +08:00
    @Mistwave
    @geelaw
    学习了学习了
    HanMeiM
        16
    HanMeiM  
    OP
       2020-03-03 21:46:00 +08:00
    @buffzty 你这个方法已经放弃了 Typescript 的优势了
    xiaojie668329
        17
    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
        18
    buffzty  
       2020-03-03 22:44:18 +08:00
    @HanMeiM 为什么去掉形参类型名叫放弃优势呢? 去掉之后什么代码也没多写,类型也自动推断了. 你不就是要这个效果吗 本来能自动推断的,你非要给他加个错误类型
    HanMeiM
        19
    HanMeiM  
    OP
       2020-03-03 23:22:31 +08:00
    @buffzty 很明显这个 Store 应该是个泛型,只不过他这里写的有问题。你实际写代码的时候,看你代码的人能通过 Store 知道什么呢?我能知道这个表单有哪些数据?这个数据是什么类型?我还是得看上下文代码。
    maichael
        20
    maichael  
       2020-03-04 09:16:12 +08:00
    @HanMeiM #12

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

    一个勉强用着的解决方法。
    LiuJiang
        21
    LiuJiang  
       2020-05-17 21:19:15 +08:00
    老哥,想问下你们 [email protected] 怎么用 ts 写表单,有代码可以参考下吗,感谢!
    HanMeiM
        22
    HanMeiM  
    OP
       2020-05-17 22:00:44 +08:00
    @LiuJiang 我个人是这么写的

    const handleFinish = (fieldsValue: IFieldsValue) => {
    ...
    }

    <Form onFinish={(value: any) => handleFinish(value)}>
    ...
    </Form>
    LiuJiang
        23
    LiuJiang  
       2020-05-18 09:40:42 +08:00
    @HanMeiM 请问 IFieldsValue 这个是啥类型?
    HanMeiM
        24
    HanMeiM  
    OP
       2020-05-18 10:21:19 +08:00
    @LiuJiang ......你自己定义的表单应该返回的类型
    uxstone
        25
    uxstone  
       2020-07-14 13:53:11 +08:00
    Antd V4 文档中的示例代码 TypeScript 和 JavaScript 是一样的
    蛋疼,感觉与 TypeScript 整合这块还没有 V3 的好用
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1002 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 23:06 · PVG 07:06 · LAX 15:06 · JFK 18:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.