【问题标题】:Is it possible to apply type constraints over a class's public methods?是否可以对类的公共方法应用类型约束?
【发布时间】:2020-11-08 18:24:48
【问题描述】:

我正在尝试定义一个类的公共方法可以返回的“可接受”类型的列表。这样做的目的是确保类方法的响应可以通过JSON.stringify 进行序列化。例如,Maps 无法安全地进行字符串化,这将导致 {} 通过网络发送。

我第一次尝试这样做是:

type TAcceptableReturns = string | number | undefined | Array<any>;

interface IReturnsAcceptable
{
    [index: string]: (() => Promise<TAcceptableReturns>) | (() => TAcceptableReturns),
}

class myActualClass implements IReturnsAcceptable
{
    [index: string]: (() => Promise<TAcceptableReturns>) | (() => TAcceptableReturns);
    private myInternalState: number;  // ERROR: Doesn't return a function

    public constructor()
    {
        this.myInternalState = 1;
    }

    public MyPubMethod(): number
    {
        return this.myInternalState;
    }
}

如您所见,这种方法不完美有两个核心原因:

  1. 它需要对实际类的实现进行扩充(需要添加[index: string]: T)。
  2. 当我只打算检查公共方法/属性时,它还将类型限制应用于类的私有属性。

有谁知道将约束应用于类的所有公共方法/属性的方法?

为了完整起见,如果您的解决方案不仅可以用于限制返回类型,还可以用于限制参数的类型,这将是很好的,尽管我目前没有这个用途。

【问题讨论】:

    标签: typescript class types constraints


    【解决方案1】:

    解决方案 1

    告诉 TypeScript 哪些字段应该符合你的蓝图。使 IReturnsAcceptable 泛型覆盖属性名称。

    type TAcceptableReturns = string | number | undefined | Array<any>;
    
    type IReturnsAcceptable<K extends string> = {
        [P in K]: (() => Promise<TAcceptableReturns>) | (() => TAcceptableReturns)
    }
    
    class myActualClass implements IReturnsAcceptable<'MyPubMethod'> {
        private myInternalState: number;
    
        public constructor() {
            this.myInternalState = 1;
        }
    
        public MyPubMethod(): number {
            return this.myInternalState;
        }
    }
    

    解决方案 2

    重新组织您的设计。让不同的类产生可序列化的数据(而不是拥有上帝类)。

    type JSONLike =
        | { [property: string]: JSONLike }
        | readonly JSONLike[]
        | string
        | number
        | boolean
        | null;
    
    // JSON.stringify looks for a `toJSON` method first.
    interface Serializable {
        toJSON(): JSONLike;
    }
    
    class MyClass implements Serializable {
        #myInternalState: number;
    
        constructor() {
            this.#myInternalState = 1
        }
    
        toJSON() {
            return this.#myInternalState
        }
    }
    
    JSON.stringify(new MyClass().toJSON());
    

    解决方案 3

    通过将您的方法与其他属性分组到一个字段中,将它们与其他属性分开。

    type TAcceptableReturns = string | number | undefined | Array<any>;
    
    interface IReturnsAcceptable {
        methods: {
            [index: string]: (() => Promise<TAcceptableReturns>) | (() => TAcceptableReturns)
        }
    }
    
    class myActualClass implements IReturnsAcceptable {
        private myInternalState: number;
    
        methods = {
            MyPubMethod: () => this.myInternalState
        }
    
        public constructor() {
            this.myInternalState = 1;
        }
    }
    
    // Usage.
    new myActualClass().methods.MyPubMethod()
    

    【讨论】:

    • 感谢您的回答。据您所知,有没有办法创建一个通用的IRemoteFriendly 接口来确保类的方法参数和返回值可以被序列化?您的示例需要根据具体情况进行调整,传入 T 或添加 toJSON 方法。
    • 据我所知,这是不可能的。使用索引签名(就像您所做的那样)将是一种方式,但没有办法“备用”不是方法的属性。您需要以某种方式将您的方法与其他属性分开。我用一个例子更新了我的答案。
    • type TSafeTypes = string | number; type TSafeMethods = (...arg: TSafeTypes[]) =&gt; TSafeTypes; 如果有办法从等式中删除数组的长度,或者更确切地说从 methodArgs -> args 数组中删除,那么应该可以创建一个类型规定所有公共方法的类型必须为TSafeMethods。类似Class[keyof Class] extends TSafeMethods
    猜你喜欢
    • 1970-01-01
    • 2020-06-28
    • 2014-02-03
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多