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

2023-09-06 10:02:53 +08:00
 lqzhgood

我有一个 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”。
    }
}
1129 次点击
所在节点    TypeScript
9 条回复
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
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
2023-09-08 18:16:19 +08:00
我的意思是,调用方这样写:
```
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
2023-09-08 18:20:24 +08:00
https://imgur.com/a/dVFMfeW

不过 intellsense 说明 TS 团队推荐的写法就是这个
chnwillliu
2023-09-11 18:15:05 +08:00
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
2023-09-11 18:35:01 +08:00
#3 借助元组进行联动缩窄的方法很巧妙。
lqzhgood
2023-09-12 09:54:13 +08:00
@chnwillliu #5

我不太明白第二点~
请教第二点的意思是 `t extends TAP_TYPE` !== `t: TAP_TYPE` 么?
对于 TAP_TYPE 是一个 enum 类型的情况下,上述应该是相等的吧? 我好像找不出反例
chnwillliu
2023-09-12 18:29:34 +08:00
@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
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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

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

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

© 2021 V2EX