Typescript 泛型,keyof 被使用,但它也用符号 | 计算编号 |字符串也是
Typescripts Generics, keyof being used, but TS also compute with symbol | number | string as well
让我们直接进入示例。
在下面的代码中你可以看到 keyof
被使用,但是打字稿仍然计算 EVT
的类型为 string|number|string
,即使 EVT
扩展 [=16] =]
代码:(最后一行自动完成和类型检查工作正常)
export const Events = {
// [event: string]: (...args: any) => void;
account: {
available: (status?: 'online'|'offline',foo?:'test',bar?:'test2') => {
console.error('1');
},
}
}
export class AMQP {
static event<
OBJ extends keyof typeof Events,
EVT extends keyof ((typeof Events)[OBJ]),
FUNC extends ((typeof Events)[OBJ][EVT])
>(
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
AMQP.event('account','available','online','test','test2');
问题(悬停在 'FUN' 上的红旗)
Type 'FUNC' does not satisfy the constraint '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][EVT]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][keyof { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ]]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string] | { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][number] | { ...; }[OBJ][symbol]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string]' is not assignable to type '(...args: any) => any'.(2344)
更新:::
截至目前。按照@Maciej Sikora 的建议,通过强制将 FUN 声明为函数修复错误来解决问题。
但这是另一个例子。在没有强制声明的情况下工作正常
唯一的区别是事件不深,所以它在那里工作,但当对象更深一点时,我仍然很想知道原因。
按条件类型缩小类型
我们可以通过明确地告诉 TS 我们只需要函数来修复它,即使目前只有看起来 TS 无法缩小它的范围。考虑:
type E = typeof Events // for readability
export class AMQP {
static event<
OBJ extends keyof E,
EVT extends keyof E[OBJ],
FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT] : never
>(
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
要点是——FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT] : never
。这意味着我们只允许在对象结构的第二层中选择函数。看来TS已经不抱怨了
这种解决方案的额外好处是,如果我们提供一个不是函数的字段,将无法使用该选择调用该函数。
为什么TS不能缩小类型
有一些原因可以部分解释。考虑类似的例子:
const a = {
a: 2
}
const f = <A extends typeof a, K extends keyof A>(a: A, k: K) => {
return a[k] + 1; // error we cannot use +
}
f(a, 'a');
如果a
里面只有数字值,我们不能使用+
是怎么回事。貌似不对?不正确,因为我们的函数说我们允许 extends typeof a
类型,所以它与 a
不同,我们可以有具有 属性 a
的对象,但也有另一个属性 b
例如布尔值。考虑以下代码编译:
const b = {
a: 2,
b: true,
}
f(b, 'b');
所以我可以使用结构上具有相同字段的对象 - a
但也可以使用另一个具有不同类型的对象。出于同样的原因,您的代码无法缩小类型,因为我们可以引入另一个结构相同但具有其他非函数属性的对象。
在我们的示例中,我们的情况略有不同,因为我们指的是一个单独的对象。然后还不完全清楚为什么在某些嵌套级别上推理失败。 TS 需要额外的帮助来缩小功能。为什么对我来说是未知的,看起来像推理限制。
为事件引入静态类型
您应该考虑的第二种解决方案是静态键入事件接口。考虑:
type Events = Record<string, Record<string, (...a: any[]) => any>>
export const events = {
account: {
available: (status?: 'online' | 'offline', foo: 'test', bar: 'test2') => {
console.error('1');
},
}
};
export class AMQP {
static event<
INSTANCE extends Events,
OBJ extends keyof INSTANCE,
EVT extends keyof INSTANCE[OBJ],
FUNC extends INSTANCE[OBJ][EVT]
>(
inst: INSTANCE,
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
// pay attention below I pass events object explicitly as first argument
AMQP.event(events, 'account', 'available', 'online', 'test', 'test2' );
请注意,我在这里介绍了一些东西:
- 事件类型
- 附加的第一个参数是事件接口的实例对象
这样的解决方案更灵活,因为实现不绑定到一个对象,你可以通过Events
接口传递任何结构正确的对象,也不需要条件类型。
让我们直接进入示例。
在下面的代码中你可以看到 keyof
被使用,但是打字稿仍然计算 EVT
的类型为 string|number|string
,即使 EVT
扩展 [=16] =]
代码:(最后一行自动完成和类型检查工作正常)
export const Events = {
// [event: string]: (...args: any) => void;
account: {
available: (status?: 'online'|'offline',foo?:'test',bar?:'test2') => {
console.error('1');
},
}
}
export class AMQP {
static event<
OBJ extends keyof typeof Events,
EVT extends keyof ((typeof Events)[OBJ]),
FUNC extends ((typeof Events)[OBJ][EVT])
>(
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
AMQP.event('account','available','online','test','test2');
问题(悬停在 'FUN' 上的红旗)
Type 'FUNC' does not satisfy the constraint '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][EVT]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][keyof { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ]]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string] | { account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][number] | { ...; }[OBJ][symbol]' is not assignable to type '(...args: any) => any'.
Type '{ account: { available: (status?: "online" | "offline" | undefined, foo: "test", bar: "test2") => void; }; }[OBJ][string]' is not assignable to type '(...args: any) => any'.(2344)
更新:::
截至目前。按照@Maciej Sikora 的建议,通过强制将 FUN 声明为函数修复错误来解决问题。
但这是另一个例子。在没有强制声明的情况下工作正常 唯一的区别是事件不深,所以它在那里工作,但当对象更深一点时,我仍然很想知道原因。
按条件类型缩小类型
我们可以通过明确地告诉 TS 我们只需要函数来修复它,即使目前只有看起来 TS 无法缩小它的范围。考虑:
type E = typeof Events // for readability
export class AMQP {
static event<
OBJ extends keyof E,
EVT extends keyof E[OBJ],
FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT] : never
>(
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
要点是——FUNC extends E[OBJ][EVT] extends (...a:any[]) => any ? E[OBJ][EVT] : never
。这意味着我们只允许在对象结构的第二层中选择函数。看来TS已经不抱怨了
这种解决方案的额外好处是,如果我们提供一个不是函数的字段,将无法使用该选择调用该函数。
为什么TS不能缩小类型
有一些原因可以部分解释。考虑类似的例子:
const a = {
a: 2
}
const f = <A extends typeof a, K extends keyof A>(a: A, k: K) => {
return a[k] + 1; // error we cannot use +
}
f(a, 'a');
如果a
里面只有数字值,我们不能使用+
是怎么回事。貌似不对?不正确,因为我们的函数说我们允许 extends typeof a
类型,所以它与 a
不同,我们可以有具有 属性 a
的对象,但也有另一个属性 b
例如布尔值。考虑以下代码编译:
const b = {
a: 2,
b: true,
}
f(b, 'b');
所以我可以使用结构上具有相同字段的对象 - a
但也可以使用另一个具有不同类型的对象。出于同样的原因,您的代码无法缩小类型,因为我们可以引入另一个结构相同但具有其他非函数属性的对象。
在我们的示例中,我们的情况略有不同,因为我们指的是一个单独的对象。然后还不完全清楚为什么在某些嵌套级别上推理失败。 TS 需要额外的帮助来缩小功能。为什么对我来说是未知的,看起来像推理限制。
为事件引入静态类型
您应该考虑的第二种解决方案是静态键入事件接口。考虑:
type Events = Record<string, Record<string, (...a: any[]) => any>>
export const events = {
account: {
available: (status?: 'online' | 'offline', foo: 'test', bar: 'test2') => {
console.error('1');
},
}
};
export class AMQP {
static event<
INSTANCE extends Events,
OBJ extends keyof INSTANCE,
EVT extends keyof INSTANCE[OBJ],
FUNC extends INSTANCE[OBJ][EVT]
>(
inst: INSTANCE,
obj: OBJ,
event: EVT,
...args: Parameters<FUNC>
) {
}
}
// pay attention below I pass events object explicitly as first argument
AMQP.event(events, 'account', 'available', 'online', 'test', 'test2' );
请注意,我在这里介绍了一些东西:
- 事件类型
- 附加的第一个参数是事件接口的实例对象
这样的解决方案更灵活,因为实现不绑定到一个对象,你可以通过Events
接口传递任何结构正确的对象,也不需要条件类型。