服了 TypeScript 这货了,符合我对微软的刻板印象,最近几年似乎每次碰到看不明白的语法出错,都是在 TypeScript ,这货无数细节太微妙了。
interface Elements {
div: { id: number; name?: string; color: string; x: string }
a: { id: number; name?: string; href: string; x: string }
}
declare function getProps<T extends Record<any, any>>(obj: T, keys: (keyof T)[]): void
function Foo<T extends 'div' | 'a'>(tag: T, props: Omit<Elements[T], 'x'>) {
// 这行报错 Type 'string' is not assignable to type 'Exclude<keyof Elements[T], "x">'
getProps(props, ['id'])
}
Foo('a', { id: 1, href: '#' })
// 我以为是因为 Omit 只能放单个对象,试了下多个也行啊。
type kkk = Omit<Elements['div' | 'a'], 'x'>
把 `function Foo<T extends 'div' | 'a'>(tag: T, props: Omit<Elements[T], 'x'>)`
改成 `function Foo<T extends 'div' | 'a'>(tag: T, props: Omit<Elements['div' | 'a'], 'x'>)` 就不会报错。
那这个 `T` 不就是 `'div' | 'a'` 吗?这 tm 二者到底有什么微妙区别导致代码一个报错一个没错?
1
ZZITE 61 天前
我觉得应该在保持第一种写法的情况下,在 getProps 传参这里使用断言处理,即 getProps(props, ["id"] as (keyof Omit<Elements[T], 'x'>)[]);
和 Omit 无关,两种情况的区别在于第一种 Omit<Elements[T], 'x'>的结果是泛型参数,这里 TS 是不会做出“ ‘id’一定存在于这个泛型中”的这种推断的,它无法确定具体的类型结构。这里的报错具体来说其实是“ “id' is not assignable to type 'Exclude<keyof Elements[T], "x">'”。但我们知道"id"是有效的 key😂,所以在传入“id”的时候对它做个类型断言。 至于第二种写法,Omit<Elements['div' | 'a']是一个具体的联合类型,ts 是可以确定整个类型结构的,也可以推断出"id"是一个符合联合类型结果的有效 key |
3
june4 OP @june4 这里的问题是 Omit<Elements[T], 'x'> 这里的 Omit 操作结果出问题了。如果去掉 Omit ,直接用 Elements[T],那一点问题都没有。 但这个 Omit 我似乎也看不出为什么会出问题?
|
4
duli950523 61 天前
@june4 #2 ts 能力是由限的,你觉得是,但是它处理起来就比较麻烦,这也是 as 的作用之一,用在你比编译器知道更多的信息,一般给泛型套上另一个 Conditional Type 大部分就不会继续计算了,所以没必要纠结这些
|
5
csl123 61 天前
Omit 改成 Exclude 好像就可以了,原因没细看。
``` typescript interface Elements { div: { id: number; name?: string; color: string; x: string } a: { id: number; name?: string; href: string; x: string } } type E<T extends 'div' | 'a'> = Exclude<keyof Elements[T], 'x'> // type EDiv = "id" | "name" | "color" type EDiv = E<'div'> // type EA = "id" | "name" | "href" type EA = E<'a'> type O<T extends 'div' | 'a'> = Omit<keyof Elements[T], 'x'> /** * type ODiv = { toString: () => string; valueOf: () => string; toLocaleString: () => string; } */ type ODiv = O<'div'> /** * type OA = { toString: () => string; valueOf: () => string; toLocaleString: () => string; } */ type OA = O<'a'> ``` |
6
ZZITE 61 天前
@june4 #3 嗯嗯,这里 Elements[T]如你所说,通过 T extends 'div' | 'a' 是可以推断出具体类型结构的,但在 Omit 后返回了一个新类型,这个新类型是抽象的,就是我说的泛型参数,这里是无法推断出 id 一定存在于新类型的类型结构里的。
|
7
june4 OP @ZZITE 也许是这样。
我正在读一个库的代码,这个是我自己抽出来改造的一部分实验代码,我这方法行不通。我的类型和库的不同,库的明显更繁琐,我突然有点理解为啥库要舍近求远那么设计类型了, 也许就是因为不那么干是条死路🐶 哎看大能的代码有一点不爽就是看不到别人的思路过程只看到最终结果。 |
8
zhangdroid 61 天前
可以看看这个 issue: https://github.com/microsoft/TypeScript/issues/39556
这样就可以 work 了: ```ts interface Elements { div: { id: number; name?: string; color: string; x: string } a: { id: number; name?: string; href: string; x: string } } declare function getProps<T extends Record<any, any>>(obj: T, keys: (keyof T)[]): void type SimpleUnionOmit<T, K extends string | number | symbol> = T extends unknown ? Omit<T, K> : never function Foo<T extends 'div' | 'a'>(tag: T, props: SimpleUnionOmit<Elements[T], 'x'>) { getProps(props, ['id']) } ``` |
9
shizhibuyu2023 61 天前
any 、 @ts-ignore 梭到底
|
10
cosmos601 61 天前
写这种复杂函数的 ts 类型是真的想死,半小时写完的业务代码,搞类型半小时
|