Typescript:无法调用 Transition 和 Selection union 的 selectAll:"This expression is not callable."
Typescript: Can't call selectAll of Transition and Selection union: "This expression is not callable."
我使用 d3
和来自 @types/d3
的类型定义。我有一个结合选择和过渡操作的方法。争论可以是任何一个。 Selection 和 Transition 类型都有 selectAll
方法。
但是当我尝试将 selectAll
应用于联合时,出现以下错误:
This expression is not callable.
Each member of the union type '{ (): Selection<null, undefined, BaseType, unknown>; (selector: null): Selection<null, undefined, BaseType, unknown>; (selector: undefined): Selection<...>; <DescElement extends BaseType, OldDatum>(selector: string): Selection<...>; <DescElement extends BaseType, OldDatum>(selector: ValueFn<...>): Selection<...>; } ...' has signatures, but none of those signatures are compatible with each other.
下面是给出此错误的代码示例:
/* Definitions from @types/d3 */
export type BaseType = Element | Document | Window | null;
export type ValueFn<T extends BaseType, Datum, Result> = (this: T, datum: Datum, index: number, groups: T[] | ArrayLike<T>) => Result;
export interface Transition<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
selectAll<DescElement extends BaseType, OldDatum>(selector: string): Transition<DescElement, OldDatum, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Transition<DescElement, OldDatum, GElement, Datum>;
}
export interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
selectAll(): Selection<null, undefined, GElement, Datum>;
selectAll(selector: null): Selection<null, undefined, GElement, Datum>;
selectAll(selector: undefined): Selection<null, undefined, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: string): Selection<DescElement, OldDatum, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Selection<DescElement, OldDatum, GElement, Datum>;
}
/* My code */
function myFunc(maybeTransition: Selection<BaseType, unknown, BaseType, unknown> | Transition<BaseType, unknown, BaseType, unknown>) {
const texts = maybeTransition.selectAll('text'); // ERROR: This expression is not callable.
// do something with texts
}
所以问题是:
- 是否可以使用现有的类型定义来解决这个问题?
- 如果不更改 Transition 或 Selection 接口就无法解决问题,应该怎么办?
问题是2个方法的类型不兼容,需要区分。工会可以解决:
function myFunc(param: { type: 'Selection', selection: Selection<BaseType, unknown, BaseType, unknown> } | { type: 'Transition', transition: Transition<BaseType, unknown, BaseType, unknown> }) {
if (param.type === 'Selection') {
const texts = param.selection.selectAll('text');
} else {
const texts = param.transition.selectAll('text');
}
// do something with texts
}
对于函数的参数 maybeTransition
,您正在指定一个 union type 期望它是两种类型的联合:
Selection<BaseType, unknown, BaseType, unknown> | Transition<BaseType, unknown, BaseType, unknown>
不幸的是 counter-intuitively,联合类型的行为与您期望的完全不同! documentation 告诉我们
A union type is a type formed from two or more other types, representing values that may be any one of those types.
到目前为止,还不错。但是,当涉及到联合类型的properties时
TypeScript will only allow an operation if it is valid for every member of the union.
如果您将接口/类型视为一组属性,从数学上讲,这将是一个交集而不是联合。我一直认为这是用词不当。小字文档也提到了一个事实:
It might be confusing that a union of types appears to have the intersection of those types’ properties. This is not an accident - the name union comes from type theory. The union number | string
is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.
查看 Selection
和 Transition
的类型定义,您会注意到这两种类型中都没有兼容的方法,留下一个没有任何属性的空类型。因此,无法在该空类型上调用方法 .selectAll()
,这基本上就是错误消息告诉您的内容。
有两种解决方法:
使用intersection types。您可以将类型定义修改为
Selection<BaseType, unknown, BaseType, unknown> & Transition<BaseType, unknown, BaseType, unknown>
虽然这会起作用,但我不鼓励使用,因为您将两个不同的接口混合成一种类型以“让它起作用”。您不是在扩展接口,也不是在丰富它,而只是混合了 .selectAll()
方法的重载定义,这感觉有些粗略。
- Narrow your typing. Preferably, you should keep using the union type and narrow the type down to either a
Selection
or a Transition
when using it. This can be done by employing an instanceof
type guard。这样,TypeScript 就能够区分这两种类型:
import { Selection, selection, Transition, transition, BaseType } from "d3";
function myFunc(
maybeTransition: Selection<BaseType, unknown, BaseType, unknown> |
Transition<BaseType, unknown, BaseType, unknown>
) {
let texts;
if (maybeTransition instanceof selection) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Selection
} else if (maybeTransition instanceof transition) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Transition
}
}
这可能看起来很笨拙,但这是由于 TypeScript 本身的 type-safety。尽管 Selection
和 Transition
在 Vanilla JavaScript 中看起来很相似,但 TypeScript 的 type-safety 有时会以更冗长的代码为代价。
您还必须记住,以上所有内容都适用于 texts
变量,它也是两种类型的联合。稍后您将不得不在代码中以相同的方式处理它。不过,根据您的整体设计,可能值得考虑一种完全不同的方法。
我使用 d3
和来自 @types/d3
的类型定义。我有一个结合选择和过渡操作的方法。争论可以是任何一个。 Selection 和 Transition 类型都有 selectAll
方法。
但是当我尝试将 selectAll
应用于联合时,出现以下错误:
This expression is not callable. Each member of the union type '{ (): Selection<null, undefined, BaseType, unknown>; (selector: null): Selection<null, undefined, BaseType, unknown>; (selector: undefined): Selection<...>; <DescElement extends BaseType, OldDatum>(selector: string): Selection<...>; <DescElement extends BaseType, OldDatum>(selector: ValueFn<...>): Selection<...>; } ...' has signatures, but none of those signatures are compatible with each other.
下面是给出此错误的代码示例:
/* Definitions from @types/d3 */
export type BaseType = Element | Document | Window | null;
export type ValueFn<T extends BaseType, Datum, Result> = (this: T, datum: Datum, index: number, groups: T[] | ArrayLike<T>) => Result;
export interface Transition<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
selectAll<DescElement extends BaseType, OldDatum>(selector: string): Transition<DescElement, OldDatum, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Transition<DescElement, OldDatum, GElement, Datum>;
}
export interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum> {
selectAll(): Selection<null, undefined, GElement, Datum>;
selectAll(selector: null): Selection<null, undefined, GElement, Datum>;
selectAll(selector: undefined): Selection<null, undefined, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: string): Selection<DescElement, OldDatum, GElement, Datum>;
selectAll<DescElement extends BaseType, OldDatum>(selector: ValueFn<GElement, Datum, DescElement[] | ArrayLike<DescElement>>): Selection<DescElement, OldDatum, GElement, Datum>;
}
/* My code */
function myFunc(maybeTransition: Selection<BaseType, unknown, BaseType, unknown> | Transition<BaseType, unknown, BaseType, unknown>) {
const texts = maybeTransition.selectAll('text'); // ERROR: This expression is not callable.
// do something with texts
}
所以问题是:
- 是否可以使用现有的类型定义来解决这个问题?
- 如果不更改 Transition 或 Selection 接口就无法解决问题,应该怎么办?
问题是2个方法的类型不兼容,需要区分。工会可以解决:
function myFunc(param: { type: 'Selection', selection: Selection<BaseType, unknown, BaseType, unknown> } | { type: 'Transition', transition: Transition<BaseType, unknown, BaseType, unknown> }) {
if (param.type === 'Selection') {
const texts = param.selection.selectAll('text');
} else {
const texts = param.transition.selectAll('text');
}
// do something with texts
}
对于函数的参数 maybeTransition
,您正在指定一个 union type 期望它是两种类型的联合:
Selection<BaseType, unknown, BaseType, unknown> | Transition<BaseType, unknown, BaseType, unknown>
不幸的是 counter-intuitively,联合类型的行为与您期望的完全不同! documentation 告诉我们
A union type is a type formed from two or more other types, representing values that may be any one of those types.
到目前为止,还不错。但是,当涉及到联合类型的properties时
TypeScript will only allow an operation if it is valid for every member of the union.
如果您将接口/类型视为一组属性,从数学上讲,这将是一个交集而不是联合。我一直认为这是用词不当。小字文档也提到了一个事实:
It might be confusing that a union of types appears to have the intersection of those types’ properties. This is not an accident - the name union comes from type theory. The union
number | string
is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.
查看 Selection
和 Transition
的类型定义,您会注意到这两种类型中都没有兼容的方法,留下一个没有任何属性的空类型。因此,无法在该空类型上调用方法 .selectAll()
,这基本上就是错误消息告诉您的内容。
有两种解决方法:
使用intersection types。您可以将类型定义修改为
Selection<BaseType, unknown, BaseType, unknown> & Transition<BaseType, unknown, BaseType, unknown>
虽然这会起作用,但我不鼓励使用,因为您将两个不同的接口混合成一种类型以“让它起作用”。您不是在扩展接口,也不是在丰富它,而只是混合了 .selectAll()
方法的重载定义,这感觉有些粗略。
- Narrow your typing. Preferably, you should keep using the union type and narrow the type down to either a
Selection
or aTransition
when using it. This can be done by employing aninstanceof
type guard。这样,TypeScript 就能够区分这两种类型:
import { Selection, selection, Transition, transition, BaseType } from "d3";
function myFunc(
maybeTransition: Selection<BaseType, unknown, BaseType, unknown> |
Transition<BaseType, unknown, BaseType, unknown>
) {
let texts;
if (maybeTransition instanceof selection) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Selection
} else if (maybeTransition instanceof transition) { // type guard
texts = maybeTransition.selectAll("text"); // texts is of type Transition
}
}
这可能看起来很笨拙,但这是由于 TypeScript 本身的 type-safety。尽管 Selection
和 Transition
在 Vanilla JavaScript 中看起来很相似,但 TypeScript 的 type-safety 有时会以更冗长的代码为代价。
您还必须记住,以上所有内容都适用于 texts
变量,它也是两种类型的联合。稍后您将不得不在代码中以相同的方式处理它。不过,根据您的整体设计,可能值得考虑一种完全不同的方法。