如何基于方法更新TypeScript类内部属性的类型
TypeScript 类方法调用后类型无法动态更新的解决方案
我有一个类,可接收HTMLElement、SVGElement或这两种元素组成的数组作为输入。希望通过强类型实现:类的方法能根据用户输入的内容,正确反映对应类型。初始化时该逻辑正常,但类提供了初始化后添加元素的方法,无法让TypeScript识别到该方法调用后的类型变化。
示例代码:
type InputTypes = HTMLElement | SVGElement function ensureArray<T>(input?: T | T[]): T[] { if(!input) return [] return Array.isArray(input) ? input : [input]; } class MyClass2<E extends InputTypes> { els?: E | [E] bar: E; constructor(m1: MyClass<E>) { this.els = m1.props.els const ensured = ensureArray<E>(this.els) this.bar = ensured[0] } } class MyClass<E extends InputTypes>{ props: { els?: E | [E] } m2: MyClass2<E> constructor(props: { els?: E | [E] }) { this.props = props this.m2 = new MyClass2(this) } getBar() { return this.m2.bar } public setBar = (els: E | [E]) => { const ensured = ensureArray<E>(els) this.m2.bar = ensured[0] } } const element = document.getElementsByClassName("bla")[0] var test1 = new MyClass({}) var foo1 = test1.getBar() var bar1 = test1.setBar(element as HTMLElement) var baz1 = test1.getBar() // Type is InputTypes instead of HTMLElement var test2 = new MyClass({ els: element as HTMLElement }) var foo2 = test2.getBar() var baz21 = test2.getBar() // HTMLElement, correct! but… var bar2 = test2.setBar(element as SVGElement) // Type 'SVGElement' is missing properties from type 'HTMLElement' var baz22 = test2.getBar()
示例中存在的问题:
test1调用setBar传入HTMLElement后,getBar返回类型仍为InputTypes,不符合预期;test2初始化传入HTMLElement后,无法传入SVGElement,且调用后getBar类型也未更新为HTMLElement | SVGElement。
解决方案
TypeScript的静态类型系统无法直接修改已有变量的类型,但可以通过泛型方法返回新类型实例的方式,实现类型的动态追踪。修改后的代码如下:
type InputTypes = HTMLElement | SVGElement function ensureArray<T>(input?: T | T[]): T[] { if (!input) return [] return Array.isArray(input) ? input : [input]; } class MyClass2<E extends InputTypes = never> { els?: E | E[] bar?: E; constructor(els?: E | E[]) { const ensured = ensureArray<E>(els) this.bar = ensured[0] this.els = els } } class MyClass<E extends InputTypes = never> { props: { els?: E | E[] } m2: MyClass2<E> constructor(props: { els?: E | E[] } = {}) { this.props = props this.m2 = new MyClass2(props.els) } getBar(): E | undefined { return this.m2.bar } public setBar<NewE extends InputTypes>(els: NewE | NewE[]): MyClass<E | NewE> { const newInstance = new MyClass<E | NewE>({ els: [...ensureArray(this.props.els), ...ensureArray(els)] as (E | NewE)[] }) return newInstance } } const element = document.getElementsByClassName("bla")[0] // 测试1:空初始化后添加元素 let test1 = new MyClass({}) let foo1 = test1.getBar() // undefined test1 = test1.setBar(element as HTMLElement) let baz1 = test1.getBar() // HTMLElement | undefined // 测试2:初始化后添加不同类型元素 let test2 = new MyClass({ els: element as HTMLElement }) let foo2 = test2.getBar() // HTMLElement | undefined test2 = test2.setBar(element as SVGElement) let baz22 = test2.getBar() // HTMLElement | SVGElement | undefined
关键说明
- 泛型累积:
MyClass的泛型E代表当前已包含的元素类型,初始默认值为never(表示无元素); - 泛型方法:
setBar定义为泛型方法,接受新元素类型NewE,返回MyClass<E | NewE>实例,实现类型的扩展; - 实例替换:由于TypeScript无法修改已有变量的类型,需要将
setBar返回的新实例赋值给原变量,以此更新类型; - 可选类型处理:
getBar返回E | undefined,明确反映元素可能不存在的情况。
内容的提问来源于stack exchange,提问作者chitzui
相关产品推荐
相关产品推荐

