【问题标题】:Typescript, How to avoid code duplication in constructor?Typescript,如何避免构造函数中的代码重复?
【发布时间】:2019-10-23 18:18:13
【问题描述】:

考虑在模型-视图-控制器场景中用作数据模型的此类(我使用的是 TypeScript 3.5):

export class ViewSource {
    private viewName : string;
    private viewStruct : IViewStruct;
    private rows : any[];
    private rowIndex : number|null;

    constructor(viewName : string) {
        // Same as this.setViewName(viewName);
        this.viewName = viewName;
        this.viewStruct = api.meta.get_view_struct(viewName);
        if (!this.viewStruct) {
            throw new Error("Clould not load structure for view, name=" + (viewName));
        }
        this.rows = [];        
        this.rowIndex = null;
    }

    public setViewName = (viewName: string) => {
        this.viewName = viewName;
        this.viewStruct = api.meta.get_view_struct(viewName);
        if (!this.viewStruct) {
            throw new Error("Clould not load structure for view, name=" + (viewName));
        }
        this.rows = [];        
        this.rowIndex = null;
    }

    public getViewStruct = ():IViewStruct => { return this.viewStruct; }

    public getCellValue = (rowIndex: number, columnName: string) : any => {
        const row = this.rows[rowIndex] as any;
        return row[columnName];
    }

}

这不是一个完整的类,我只包含了几个方法来演示问题。 ViewSource 是一个可变对象。可以从应用程序的多个部分引用它。 (请注意,可变对象是事实。这个问题不是关于选择使用不可变对象的不同数据模型。)

每当我想更改ViewSource 对象的状态时,我都会调用它的setViewName 方法。它确实有效,但也非常笨拙。构造函数中的每一行代码都在setViewName 方法中重复。

当然不能使用这个构造函数:

constructor(viewName : string) {
    this.setViewName(viewName);
}

因为这会导致 TS2564 错误:

Property 'viewStruct' has no initializer and is not definitely assigned in the constructor.ts(2564)

我一般不想忽略 TS2564 错误。但我也不想重复所有属性初始化。我还有一些其他类的属性更多(>10),相应的代码重复看起来很难看,而且容易出错。 (我可能忘记了有些东西必须通过两种方法进行修改......)

那么如何避免重复多行代码呢?

【问题讨论】:

    标签: typescript3.0


    【解决方案1】:

    我认为在这种情况下避免代码重复的最佳方法是创建一个包含初始化代码的函数,但不是设置值,而是返回需要设置的值。
    类似于以下内容:

    export class ViewSource {
        private viewName : string;
        private viewStruct : IViewStruct;
        private rows : any[];
        private rowIndex : number|null;
    
        constructor(viewName : string) {
            const {newViewName, newViewStruct, newRows, newRowIndex} = this.getNewValues(viewName);
            this.viewName = newViewName;
            this.newViewStruct = newViewStruct;
            // Rest of initialization goes here
        }
    
        public setViewName = (viewName: string) => {
            const {newViewName, newViewStruct, newRows, newRowIndex} = this.getNewValues(viewName);
            // Rest of initialization goes here
        }
    
        privat getNewValues = (viewName) => {
            const newViewName = viewName;
            const newViewStruct = api.meta.get_view_struct(viewName);
            if (!newViewStruct) {
                throw new Error("Clould not load structure for view, name=" + (viewName));
            }
            const newRows = [];        
            const newRowIndex = null;
            return {newViewName, newViewStruct, newRows, newRowIndex};
        }
    
    }
    

    这样你唯一复制的就是设置值,而不是计算它们,如果值计算变得更复杂,你可以简单地扩展返回的值。

    【讨论】:

    • 是的,我希望 { ...this } = this.getNewValues(viewName),这可能是 TypeScript 语法的一个很好的扩展。
    • 您可以将所有值放在一个对象中并像这样设置它们:this.values = this.getNewValues()
    • 是的,但是我必须在任何地方都使用 this.values.propName 而不是 this.propName。
    【解决方案2】:

    比公认的答案更简单的方法是在其他地方初始化的每个成员上方使用 //@ts-ignore[1] 注释。

    考虑这个人为的例子

    class Foo {
        // @ts-ignore TS2564 - initialized in the init method
        a: number;
    
        // @ts-ignore TS2564 - initialized in the init method
        b: string;
    
        // @ts-ignore TS2564 - initialized in the init method
        c: number;
        
        constructor(a: number, b: string) {
            if(a === 0) {
                this.init(a,b,100);
            } else {
                this.init(a,b,4912);
            }           
        }
    
        private init(a: number, b: string, c: number): void {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }
    

    由于 TypeScript 3.9 存在 //@ts-expect-error[2] 注释,但我认为 @ts-ignore 是合适的。

    [1]Suppress errors in .ts files
    [2]TS expect errors comment

    【讨论】:

    • 问原始问题时,没有Typescript 3.9。不过,我更喜欢这个而不是公认的解决方案。它仍然不完美。 { ... this } = this.init() 是最好的,但仍然不支持。
    • @nagylz 确实 @ts-expect-error 是非常新的(而不是我在这种情况下使用的)。据我所知,@ts-ignore 自 Typescript 2.6 以来一直存在。我同意对{ ... this } = this.init() 的某种内置支持,而不是使用 ts-cmets 填充代码,会更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-14
    • 2013-07-08
    相关资源
    最近更新 更多