[求助] 函数中使用泛型缩小参数类型 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lqzhgood
V2EX    TypeScript

[求助] 函数中使用泛型缩小参数类型

  •  
  •   lqzhgood 2023-09-06 10:02:53 +08:00 1593 次点击
    这是一个创建于 830 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我有一个 Map ,一一对应。

    TAP_TYPE.LOCAL 对应 TAP_LOCAL,

    TAP_TYPE.MAP 对应 TAP_MAP

    我在函数中已经通过 switch 约束 test 的 v, 为什么 ts 还是推导 v 的类型是 TAP_LOCAL | TAP_MAP

    或者对这样的例子,除了 v 进行强制断言,有什么更好的写法吗?

    enum TAP_TYPE { 'LOCAL', 'MAP', } interface TAP_LOCAL { a: string; } interface TAP_MAP { b: string; } type TapTypeMap = { [TAP_TYPE.LOCAL]: TAP_LOCAL; [TAP_TYPE.MAP]: TAP_MAP; }; function test<T extends TAP_TYPE>(t: T, v: TapTypeMap[T]) { switch (t) { case TAP_TYPE.LOCAL: return v.a; // 类型错误 类型“TAP_LOCAL | TAP_MAP”上不存在属性“a”。 case TAP_TYPE.MAP: return v.b; // 类型错误 类型“TAP_LOCAL | TAP_MAP”上不存在属性“b”。 } } 
    9 条回复    2023-09-30 22:16:35 +08:00
    Opportunity
        1
    Opportunity  
       2023-09-07 00:09:19 +08:00
    当 T=TAP_TYPE 有 t: TAP_TYPE, v: TAP_LOCAL | TAP_MAP 。
    此时,t 和 v 没有任何关系,你对 t 再怎么判断也不应当影响 v 的类型,我觉得 ts 的推断没有任何问题。

    我觉得断言已经是最好的方案了,接口上你可以选择使用重载代替泛型,避免 T=TAP_TYPE 这种情况,实现没啥好办法。
    lqzhgood
        2
    lqzhgood  
    OP
       2023-09-07 16:18:32 +08:00
    @Opportunity #1
    我理解给参数 v TapTypeMap[T] 类型就是让 ts 知道 “一一对应的关系”

    switch 外层的 v 类型是 TAP_LOCAL | TAP_MAP 没错
    但是通过 switch 缩小 t 的范围,关联到 TapTypeMap[T] 从而缩小 v 的范围我觉得也没问题吧~

    后来我想通过函数重载的方式去实现也一样报错了~

    ```ts
    function test(t: TAP_TYPE.LOCAL, v: TAP_LOCAL);
    function test(t: TAP_TYPE.MAP, v: TAP_MAP);
    function test(t: TAP_TYPE, v: TAP_LOCAL | TAP_MAP) {
    switch (t) {
    case TAP_TYPE.LOCAL:
    return v.a; //报错
    case TAP_TYPE.MAP:
    return v.b; //报错
    }
    }
    ```

    顺着重载的思路搜到这个 2020 年的帖子 https://www.zhihu.com/question/402139008
    问题类似,也没解决~
    Opportunity
        3
    Opportunity  
       2023-09-08 18:16:19 +08:00   1
    我的意思是,调用方这样写:
    ```
    const t: TAP_TYPE = TAP_TYPE.LOCAL
    test(t, {b:'xx'})
    ```
    TS 不会报任何错误,运行时会炸。用函数重载可以在运行时就报错。

    如果你硬要把接口搞成这样,就要想办法告诉 TS 两个参数的联系,比如这样写:

    ```
    function test(...[t, v]: [t: TAP_TYPE.LOCAL, v: TAP_LOCAL] | [t: TAP_TYPE.MAP, v: TAP_MAP]) {
    switch (t) {
    case TAP_TYPE.LOCAL:
    return v.a;
    case TAP_TYPE.MAP:
    return v.b;
    }
    }
    ```
    但是说实话,太丑了,我更倾向于用 as
    Opportunity
        4
    Opportunity  
       2023-09-08 18:20:24 +08:00
    https://imgur.com/a/dVFMfeW

    不过 intellsense 说明 TS 团队推荐的写法就是这个
    chnwillliu
        5
    chnwillliu  
       2023-09-11 18:15:05 +08:00   1
    1. switch (t) 缩窄的是变量 t 的类型,并不会影响泛型 T 的范围,就算 T 真能跟随 case 缩窄变化,v:TapTypeMap[T] 也不能获得联缩窄。T 是一个未知类型,extends 只是约束了这个未知的边界。
    2. t 的类型是 TAP_TYPE 的子类型,导致 case 对 t 的类型缩窄失效,此时 t 的类型不再是可缩窄类型。TAP_TYPE 是 enum 类型,类似 union type ,可以缩窄,但从 enum 派生出去的类型不一定可缩窄。


    enum TAP_TYPE {
    LOCAL,
    MAP,
    }

    interface TAP_LOCAL {
    a: string;
    }

    interface TAP_MAP {
    b: string;
    }

    type TapTypeMap = {
    [TAP_TYPE.LOCAL]: TAP_LOCAL;
    [TAP_TYPE.MAP]: TAP_MAP;
    };

    function test<T extends TAP_TYPE>(t: T, v: TapTypeMap[T]): string;
    function test(t: TAP_TYPE, v: TapTypeMap[TAP_TYPE]): string {
    switch (t) {
    case TAP_TYPE.LOCAL:
    return (v as TapTypeMap[typeof t]).a;
    case TAP_TYPE.MAP:
    return (v as TapTypeMap[typeof t]).b;
    }
    }
    chnwillliu
        6
    chnwillliu  
       2023-09-11 18:35:01 +08:00
    #3 借助元组进行联动缩窄的方法很巧妙。
    lqzhgood
        7
    lqzhgood  
    OP
       2023-09-12 09:54:13 +08:00
    @chnwillliu #5

    我不太明白第二点~
    请教第二点的意思是 `t extends TAP_TYPE` !== `t: TAP_TYPE` 么?
    对于 TAP_TYPE 是一个 enum 类型的情况下,上述应该是相等的吧? 我好像找不出反例
    chnwillliu
        8
    chnwillliu  
       2023-09-12 18:29:34 +08:00 via Android
    @lqzhgood

    这里就要说到 ts 的 nominal type checking.

    type foo = 0 & {brand: 'foo'}

    这里 foo 并不会是 never ,这是 ts 刻意为之的,虽然运行时不可能存在一种值满足这个类型。(但是可以在 ts 层面 as 啊)

    type bar = (0 & {brand: 'foo'}) extends 0 ? true : false; // true

    function test<T extends TAP_TYPE>(t: T, v: TapTypeMap[T])

    T 实际有可能是 TAP_TYPE.LOCAL & {a:1} 或与任意其他 interface 的交叉类型。
    lqzhgood
        9
    lqzhgood  
    OP
       2023-09-30 22:16:35 +08:00
    看到个 Ts 的 issue ,和这个问题相关 https://github.com/Microsoft/TypeScript/issues/22609

    #########################################################

    function add(x:string,y:string):string;
    function add(x:number, y:number):number;

    //实现签名 对外不可见
    function add(x:string|number, y: number|string): number | string{
    if(typeof x === 'string'){
    return x + ',' + y;
    }else {
    return x.toFixed() + (y as number).toFixed();
    // 很不幸,ts 暂时不支持对函数重载后续参数的 narrowing 操作,如这里对 x 做了 type narrowing 但是对 y 没有做 narrowing ,需要手动的 y 做 type assert 操作
    https://github.com/Microsoft/TypeScript/issues/22609
    }
    }

    作者:小电前端团队
    链接: https://juejin.cn/post/6912309038743191559
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     908 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 18:25 PVG 02:25 LAX 10:25 JFK 13:25
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86