【问题标题】:Typescript method return type based on attributes基于属性的打字稿方法返回类型
【发布时间】:2021-05-16 18:17:03
【问题描述】:

Typescript 的条件: T extends U ? X : Y 语法非常强大,但我还没有找到一种方法来根据类的属性或函数本身的某些内容来指定返回的类型。例如,以这个简单的类为例:

class X {
    members: number[] = [];
    returnRoman = false;

    first(): number|string {
        if (!this.returnRoman) {
            return members[0];
        } else {
            return numberToRomanNumeral(members[0]);
        }
    }
}

有没有办法将返回的类型缩小为简单的数字或字符串以进行类型检查?我遇到的特殊问题是我有这样的结构:

class Base {
    x: number = 0;
}

class Sub extends Base { 
    y: number = 0;
}

class FilterIterator {
    members: Base[] = [];
    filterClass: typeof Base = undefined;

    class first(): Base {
        for (const m of this.members) {
            if (this.filterClass !== undefined && !(m instanceof this.filterClass)) {
                continue;
            }
            return m;
        }
    }
}

const fi = FilterIterator();
fi.filterClass = Sub
fi.members = [new Sub(), new Sub()];

const s = fi.first();
if (s.y > 0) {
   console.log('s is moving up in the world!');
}

因为fi.first() 被键入以返回Base,所以if (s.y > 0) 行会引发错误,因为y 在Base 上不存在。在这种情况下,它不会太难做到:

const s = fi.first() as Sub;

但如果我们有这样的东西:

class FilterIterator extends Base {
    ...
    filterClass = FilterIterator;  // I know, not possible simply, but there are ways...
}

然后你会得到如下代码:

const fi = FilterIterator();
[ set up .members with nested FilterIterators ]
const firstOfFirstOfFirst = fi.first().first().first();

被改写为:

const firstOfFirstOfFirst = (((fi.first() as FilterIterator).first() 
    as FilterIterator).first() as FilterIterator);

如果我们实际创建可以返回生成器等的生成器,情况会变得更糟,因为在for...of 循环中声明类型还不可能。它还将确定返回值应该是什么的逻辑转移到使用软件,FilterIterator 或 Base 本身应该知道可能返回哪些类。

欢迎任何解决方案或改进。

【问题讨论】:

    标签: typescript subclass typescript-generics typescript-class


    【解决方案1】:

    您的简化示例更难正确键入,因为returnRoman 是一个实例变量,可以假定在同一个实例中多次更改其值。您必须通过 setter 更新它并使用语法 asserts this is this & SomeType

    您的实际用例可能会遇到需要在设置类型后转换类型的相同困难,但我们可以通过将filterClass 作为构造函数中的参数来规避这些问题。这也意味着filterClass 的类型总是可以为实例正确推断,因为从创建实例的那一刻起就知道它。所以它成为generic classes 的直接用例。

    type Constructor<T> = new (...args: any[]) => T;
    
    class FilterIterator<T> {
        members: (T | Base)[] = [];
        private filterClass: Constructor<T>;
    
        constructor( filterClass: Constructor<T> ) {
            this.filterClass = filterClass;
        }
    
        first(): T {
            for (const m of this.members) {
                if (m instanceof this.filterClass) {
                    return m;
                }
            }
            // you need to either throw an error or expand your return type to include undefined
            throw new Error("no match found");
        }
    }
    

    instanceof 是 typescript 中的 automatic type guard,所以如果你用return m 打线,typescript 就知道m 保证与filterClass 的类型相同T

    我认为您正在尝试做一些不同的事情,其中​​过滤器是某种自定义检查而不是类构造函数,但我没有足够的信息来真正正确地键入它。您可以将过滤器定义为user-defined type guard

    type Filter<T> = (value: any) => value is T;
    
    class CustomFilterIterator<T> {
        members: any[] = [];
        private filter: Filter<T>;
    
        constructor( filter: Filter<T> ) {
            this.filter = filter;
        }
    
        first(): T | undefined {
            for (const m of this.members) {
                if (this.filter(m)) {
                    return m;
                }
            }
            return;
        }
    }
    
    const isNumber = (value: any): value is number => {
        return typeof value === "number"
    }
    
    const numberFilter = new CustomFilterIterator(isNumber);
    numberFilter.members = [55, "55", "hello", 78.3];
    numberFilter.first()?.toExponential();
    

    Typescript Playground Link

    【讨论】:

    • 谢谢!我认为这是思考问题的一种很好的新方法,我会看看我是否可以在实现中使用它,并接受它是否可以通过微小的调整来工作。
    猜你喜欢
    • 1970-01-01
    • 2018-07-01
    • 1970-01-01
    • 2019-12-06
    • 1970-01-01
    • 1970-01-01
    • 2020-06-29
    • 2020-06-03
    • 2018-01-21
    相关资源
    最近更新 更多