【问题标题】:How do I split a TypeScript class into multiple files?如何将 TypeScript 类拆分为多个文件?
【发布时间】:2014-07-15 14:53:16
【问题描述】:

我找到了很多示例,也尝试将一个模块拆分为多个文件。所以我得到了那个,非常方便。但有时出于同样的原因拆分班级也很实用。假设我有几种方法,我不想把所有东西都塞进一个长文件中。

我正在寻找类似于 C# 中的 partial 声明的东西。

【问题讨论】:

  • IMO,将应该代表 single responsibility 的内容拆分为多个文件是没有意义的。 partial C# 声明在部分代码是自动生成的代码时确实会发光,但您的 TypeScript 不会出现这种情况,对吧?您的班级是否承担了太多角色?你能把它 >1 课吗?
  • 你说得有道理。也许我可以将所有方法分解成一个可以充当帮助器的模块,而不是在类上使用帮助器方法。我会考虑的。
  • 我不同意第一条评论。我多次发现,跨多个文件分隔的部分类非常有用。这主要是当您有一个开发团队在同一个课程上一起工作时。
  • @spender:当您想要一个部分由手工编写并部分由代码生成器编写的类时,这也很有帮助。
  • 我在这里是因为我的类(非常简单的 CRUD)由于重载和类型定义几乎有 1k 行。哎哟。

标签: javascript typescript


【解决方案1】:

模块让您可以从另一个文件扩展打字稿类:

user.ts

export class User {
  name: string;
}

import './user-talk';

user-talk.ts

import { User } from './user';

class UserTalk {
  talk (this:User) {
    console.log(`${this.name} says relax`);
  }
}

User.prototype.sayHi = UserTalk.prototype.sayHi;

declare module './user' {
  interface User extends UserTalk { }
}

用法:

import { User } from './user';

const u = new User();
u.name = 'Frankie';
u.talk();
> Frankie says relax

如果你有很多方法,你可以试试这个:

// user.ts
export class User {
  static extend (cls:any) {
    for (const key of Object.getOwnPropertyNames(cls.prototype)) {
      if (key !== 'constructor') {
        this.prototype[key] = cls.prototype[key];
      }
    }
  }
  ...
}

// user-talk.ts
...
User.extend(UserTalk);

或者将子类添加到原型链中:

...
static extend (cls:any) {
  let prototype:any = this;
  while (true) {
    const next = prototype.prototype.__proto__;
    if (next === Object.prototype) break;
    prototype = next;
  }
  prototype.prototype.__proto__ = cls.prototype;
}

【讨论】:

    【解决方案2】:

    我个人使用@partial装饰器作为一种简化语法,可以帮助将单个类的功能划分为多个???类文件...https://github.com/mustafah/partials

    【讨论】:

      【解决方案3】:

      提议模式的修改版本。

      // temp.ts contents
      import {getValue, setValue} from "./temp2";
      
      export class BigClass {
          // @ts-ignore - to ignore TS2564: Property 'getValue' has no initializer and is not definitely assigned in the constructor.
          public getValue:typeof getValue;
      
          // @ts-ignore - to ignore TS2564: Property 'setValue' has no initializer and is not definitely assigned in the constructor.
          public setValue:typeof setValue;
          protected value = "a-value";
      }
      
      BigClass.prototype.getValue = getValue;
      BigClass.prototype.setValue = setValue;
      
      //======================================================================
      // temp2.ts contents
      import { BigClass } from "./temp";
      
      export function getValue(this: BigClass) {
          return this.value;
      }
      
      export function setValue(this: BigClass, value: string ) {
          this.value = value;
      }
      

      优点

      • 不在类实例中创建额外的字段,因此没有开销:在构造、销毁时,不使用额外的内存。 typescript 中的字段声明仅用于此处的输入,它们不会在 Javascript 运行时创建字段。
      • 智能正常(在 Webstorm 中测试)

      缺点

      • 需要ts-ignore
      • 比@Elmer 的答案更丑的语法

      解的其余性质相同。

      【讨论】:

        【解决方案4】:

        最近我使用这种模式:

        // file class.ts
        import { getValue, setValue } from "./methods";
        
        class BigClass {
            public getValue = getValue;
            public setValue = setValue;
        
            protected value = "a-value";
        }
        
        // file methods.ts
        import { BigClass } from "./class";
        
        function getValue(this: BigClass) {
            return this.value;
        }
        
        function setValue(this: BigClass, value: string ) {
           this.value = value;
        }
        

        这样我们可以将方法放在一个单独的文件中。现在这里发生了一些循环依赖的事情。文件class.tsmethods.ts 导入,methods.tsclass.ts 导入。这可能看起来很可怕,但这不是问题。只要代码执行不是循环的,一切都很好,在这种情况下,methods.ts 文件不会执行class.ts 文件中的任何代码。 NP!

        您也可以将它与这样的泛型类一起使用:

        class BigClass<T> {
            public getValue = getValue;
            public setValue = setValue;
        
            protected value?: T;
        }
        
        function getValue<T>(this: BigClass<T>) {
            return this.value;
        }
        
        function setValue<T>(this: BigClass<T>, value: T) {
            this.value = value;
        }
        

        【讨论】:

        • 这看起来是一个很棒的解决方案,我将在下一个项目中与我的团队一起尝试一下!
        • @Elmer 这很好用!对于任何好奇的人,它记录在 herethis 参数)
        • 不错的解决方案,我发现的唯一问题是您不能在外部函数中调用类私有方法。
        • @Galactus 没错!但您可以访问受保护的成员!它不一样,但它可能是一个(可能是肮脏的)解决方案。
        • @bmarti44 这是个好问题!我实际上从未尝试过使用泛型类,经过一番摆弄后我更新了答案!
        【解决方案5】:

        为了添加到@Elmer 的solution,我添加了以下内容以使其在单独的文件中工作。

        some-function-service-helper.ts

        import { SomeFunctionService } from "./some-function-service";
        
        export function calculateValue1(this: SomeFunctionService) {
        ...
        }
        

        some-function-service.ts

        import * as helper from './some-function-service-helper';
        
        @Injectable({
            providedIn: 'root'
        })
        export class SomeFunctionService {
        
            calculateValue1 = helper.calculateValue1;  //  helper function delcaration used for getNewItem
        
            public getNewItem() {
                var one = this.calculateValue1();
            }
        

        【讨论】:

        • 这种方法看起来很棒,但实际上它有一个很大的缺点(与上面的 for 相同):它实际上在类中创建了额外的字段,而不是为此使用原型。如果您有许多该类的实例,这可能会大大增加内存使用量并且会使代码变得更慢,并且此类实例的创建和删除也可能会更慢。因此,如果性能不是问题,这种方法似乎是适用的。
        【解决方案6】:

        为什么不直接使用 js 自带的Function.call

        class-a.ts

        Class ClassA {
          bitten: false;
        
          constructor() {
            console.log("Bitten: ", this.bitten);
          }
        
          biteMe = () => biteMe.call(this);
        }
        
        

        在其他文件中bite-me.ts

        export function biteMe(this: ClassA) {
          // do some stuff
          // here this refers to ClassA.prototype
        
          this.bitten = true;
        
          console.log("Bitten: ", this.bitten);
        }
        

        // 使用它

        const state = new ClassA();
        // Bitten: false
        
        state.biteMe();
        // Bitten: true
        

        更多信息请查看Function.call的定义

        【讨论】:

        • 这种方法看起来很棒,但实际上它有一个很大的缺点:它实际上在类中创建了额外的字段,而不是为此使用原型。如果您有许多该类的实例,这可能会大大增加内存使用量并且会使代码变得更慢,并且此类实例的创建和删除也可能会更慢。因此,如果性能不是问题,这种方法似乎是适用的。
        【解决方案7】:

        您可以使用multi file namespaces

        验证.ts:

        namespace Validation {
            export interface StringValidator {
                isAcceptable(s: string): boolean;
            }
        }
        

        LettersOnlyValidator.ts(使用上面的 StringValidator):

        /// <reference path="Validation.ts" /> 
        namespace Validation {
            const lettersRegexp = /^[A-Za-z]+$/;
            export class LettersOnlyValidator implements StringValidator {
                isAcceptable(s: string) {
                    return lettersRegexp.test(s);
                }
            }
        }
        

        Test.ts(使用上面的 StringValidator 和 LettersOnlyValidator):

        /// <reference path="Validation.ts" />
        /// <reference path="LettersOnlyValidator.ts" />
        
        // Some samples to try
        let strings = ["Hello", "101"];
        
        // Validators to use
        let validators: { [s: string]: Validation.StringValidator; } = {};
        validators["Letters only"] = new Validation.LettersOnlyValidator();
        

        【讨论】:

          【解决方案8】:

          我们可以通过prototypeInterface的定义逐步扩展类方法:

          import login from './login';
          import staffMe from './staff-me';
          
          interface StaffAPI {
            login(this: StaffAPI, args: LoginArgs): Promise<boolean>;
            staffsMe(this: StaffAPI): Promise<StaffsMeResponse>;
          }
          
          class StaffAPI {
            // class body
          }
          
          StaffAPI.prototype.login = login;
          StaffAPI.prototype.staffsMe = staffsMe;
          
          export default StaffAPI;
          

          【讨论】:

            【解决方案9】:

            在将使用“原型”的大型旧多文件 javascript 类转换为多个打字稿文件时,我使用普通子类化:

            bigclassbase.ts:

            class BigClassBase {
                methodOne() {
                    return 1;
                }
            
            }
            export { BigClassBase }
            

            bigclass.ts:

            import { BigClassBase } from './bigclassbase'
            
            class BigClass extends BigClassBase {
                methodTwo() {
                    return 2;
                }
            }
            

            您可以在任何其他打字稿文件中导入 BigClass。

            【讨论】:

            • 但我不能扩展超过 1 个类。如何“扩展” 100 个课程?
            • 在这种情况下,您可以使用Mixin,顺便说一句,我从不使用它们……我更喜欢组合模式。
            【解决方案10】:

            你不能。

            有一个实现部分类的功能请求,首先是在 CodePlex 上,后来在 GitHub 上,但 on 2017-04-04 被声明为超出范围。给出了很多理由,主要的收获似乎是他们希望尽可能地避免偏离 ES6:

            TypeScript已经有太多 TS 特定的类特性 [...] 添加另一个 TS 特定的类特性是压倒骆驼的另一根稻草,如果可以的话,我们应该避免。 [...] 因此,如果有一些场景真的因为添加部分类而被淘汰出局,那么该场景应该能够通过 TC39 流程证明自己的合理性。

            【讨论】:

            • 如果 typescript 的目标是保持接近 ES6,那它为什么存在呢?我们已经有了 ES6
            • @Jose TypeScript 于 2012 年 10 月公开发布,比 ES6 规范最终确定早了近 3 年。当时情况完全不同,以有组织的方式编写 JavaScript 非常具有挑战性。 TypeScript 不是唯一的替代语言,例如 CoffeeScript 和 Dart。从那时起,JavaScript 有了很大的发展,部分归功于这些项目。今天,TypeScript 提供的最重要的特性是静态类型。
            • @Stijn 完全同意。但我们现在有 Flow。到目前为止,ES# + Flow 每次都击败 Typescript,而且是转译而不是编译!我相信 Typescript 能提供灵感和竞争是很可爱的,让 Babel 和 Flow 不断变得更好。但我永远不会选择性能更好、更优雅、功能更丰富的 ES# + Flow 的东西。你会吗??
            • @EdoardoL'Astorina 在过去的几年里我没有做过很多前端开发,事实上这是我第一次听说 Flow,所以我无法评论它。但我对出现替代品并不感到惊讶。
            • @Jose ES6(以及 TS)试图将 JS 转变为 OOP。它不是 OOP,它是具有类 C 语法的 LISP。数据类型是 LISP 数据类型,隐式类型转换是 LISP 行为,闭包是 LISP 闭包。如果您了解 LISP,this 的行为甚至一点都不令人惊讶,奇怪 都是 OOP 的伪装。创建 TS 和 ES6 的人可能确实了解这一点,他们试图重新制作语言以匹配其用户的误解并验证无效。
            【解决方案11】:

            我使用这个(在 typescript 2.2.2 中工作):

            class BigClass implements BigClassPartOne, BigClassPartTwo {
                // only public members are accessible in the
                // class parts!
                constructor(public secret: string) { }
            
                // this is ugly-ish, but it works!
                methodOne = BigClassPartOne.prototype.methodOne;
                methodTwo = BigClassPartTwo.prototype.methodTwo;
            }
            
            class BigClassPartOne {
                methodOne(this: BigClass) {
                    return this.methodTwo();
                }
            }
            
            class BigClassPartTwo {
                methodTwo(this: BigClass) {
                    return this.secret;
                }
            }
            

            【讨论】:

            • 这种“原型设计”解决方法会扼杀智能感知吗?
            • 不!它是智能感知友好的(至少在 vscode 中)
            • 您应该将部件的方法设为静态(static methodOne(this: BigClass) {...})以摆脱.prototype
            • this -> stackoverflow.com/questions/23876782/… 是更好的解决方案!
            • @Elmer,这种方法看起来很棒,但实际上它有一个很大的缺点(与上面的方法相同):它实际上在类中创建了额外的字段,而不是为此使用原型。如果您有许多该类的实例,这可能会大大增加内存使用量并且会使代码变得更慢,并且此类实例的创建和删除也可能会更慢。因此,如果性能不是问题,这种方法似乎是适用的。
            猜你喜欢
            • 2019-11-06
            • 1970-01-01
            • 1970-01-01
            • 2016-05-05
            • 1970-01-01
            • 1970-01-01
            • 2017-02-27
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多