TypeScript中D3.js重写特定SVG元素选择方法的报错排查
你遇到的问题核心在于TypeScript中泛型类型的协变与逆变,以及D3的Selection类型内部的结构设计。
先看你能正常运行的Gen<T>例子:你的Gen接口只包含一个属性prop: T,这里的T是协变的——也就是说,如果SVGRectElement是SVGElement的子类,那么Gen<SVGRectElement>可以安全地赋值给Gen<SVGElement>,因为子类实例完全符合父类的类型要求。
但D3的Selection类型不一样,它内部包含大量方法(比如datum、on),这些方法的签名涉及到函数类型的逆变。举个关键的例子:Selection的on方法接受的回调函数中,this的类型是当前选中的元素类型。对于Selection<SVGRectElement, ...>,回调的this是SVGRectElement;而对于Selection<SVGElement, ...>,回调的this是SVGElement。
根据TypeScript的类型规则,函数的参数(包括隐式的this参数)是逆变的:如果有一个函数类型(this: Parent) => void,你不能把一个(this: Child) => void的函数赋值给它——因为后者的this范围更窄,当父类的代码调用这个函数时,类型检查会认为存在潜在的不匹配风险(虽然实际中SVGRectElement是SVGElement的子类不会有问题,但TypeScript是基于结构做静态检查的)。
正是因为Selection内部这些逆变的函数类型,导致Selection<SVGRectElement, ...>不能被视为Selection<SVGElement, ...>的子类型,所以子类重写方法时出现类型不兼容的错误。
解决这个问题的最佳方式是让父类使用泛型,这样子类可以指定具体的元素类型,避免类型不兼容:
import * as d3 from "d3"; class ElementSelector<T extends SVGElement = SVGElement> { public getSelection(): d3.Selection<T, any, any, any> { // 示例中用body占位,实际可替换为你的选择器 return d3.select<T, any>("body") as d3.Selection<T, any, any, any>; } } class RectSelector extends ElementSelector<SVGRectElement> { public getSelection(): d3.Selection<SVGRectElement, any, any, any> { return d3.select<SVGRectElement, any>("rect"); } }
这种方式下,RectSelector继承的是ElementSelector<SVGRectElement>,父类的getSelection方法返回的就是针对SVGRectElement的Selection,子类重写时类型完全匹配,不会有错误。
如果你不想修改父类的结构,另一种(不推荐)的方式是使用类型断言绕过检查,但这会失去TypeScript的类型安全保障:
class RectSelector extends ElementSelector { public getSelection(): d3.Selection<SVGRectElement, any, any, any> { return d3.select<SVGRectElement, any>("rect") as unknown as d3.Selection<SVGElement, any, any, any>; } }
内容的提问来源于stack exchange,提问作者Marcoq

