Các lớp tĩnh TypeScript


145

Tôi muốn chuyển sang TypeScript từ JS truyền thống vì tôi thích cú pháp giống C #. Vấn đề của tôi là tôi không thể tìm ra cách khai báo các lớp tĩnh trong TypeScript.

Trong C #, tôi thường sử dụng các lớp tĩnh để tổ chức các biến và phương thức, đặt chúng lại với nhau trong một lớp được đặt tên mà không cần phải kích hoạt một đối tượng. Trong vanilla JS, tôi đã từng làm điều này với một đối tượng JS đơn giản:

var myStaticClass = {
    property: 10,
    method: function(){}
}

Trong TypeScript, tôi thà đi theo cách tiếp cận C-sharpy của mình, nhưng dường như các lớp tĩnh không tồn tại trong TS. Giải pháp thích hợp cho vấn đề này là gì?

Câu trả lời:


166

TypeScript không phải là C #, vì vậy bạn không nên mong đợi các khái niệm tương tự về C # trong TypeScript. Câu hỏi là tại sao bạn muốn các lớp tĩnh?

Trong C #, một lớp tĩnh chỉ đơn giản là một lớp không thể được phân lớp và chỉ chứa các phương thức tĩnh. C # không cho phép một người xác định các chức năng bên ngoài các lớp. Trong TypeScript, điều này là có thể, tuy nhiên.

Nếu bạn đang tìm cách đưa các hàm / phương thức của mình vào một không gian tên (nghĩa là không phải là toàn cục), bạn có thể xem xét sử dụng các mô-đun của TypeScript, ví dụ:

module M {
    var s = "hello";
    export function f() {
        return s;
    }
}

Vì vậy, bạn có thể truy cập Mf () bên ngoài, nhưng không phải s và bạn không thể mở rộng mô-đun.

Xem đặc tả TypeScript để biết thêm chi tiết.


vì vậy một mô-đun có thể có một phương thức tĩnh, nhưng một lớp không thể? nhưng các mô-đun không thể có dữ liệu tĩnh. Nó không thuận tiện như JS để gói dữ liệu và mã mà không phải khởi tạo bất cứ thứ gì.
dcsan

Nó có thể hữu ích để bao gồm rằng bạn sẽ cần bao gồm .jstrong html. Vì vậy, đối với Angular 2bạn có lẽ bạn đang sử dụng System... vì vậy hãy làm System.import("Folder/M");(hoặc bất kỳ đường dẫn nào đến .jstệp đã biên dịch ) trướcbootstrap import
Serj Sagan

11
Điều này không được chấp nhận. Ngoài ra tslint sẽ không cho phép bạn làm điều đó nữa cho các mô-đun và không gian tên. Đọc ở đây: palantir.github.io/tslint/rules/no-namespace
Florian Leit Quay

Tôi có một trường hợp sử dụng để có các lớp tĩnh. Ngay bây giờ, tôi có một lớp chỉ chứa các phương thức tĩnh. Trong dự án, chúng tôi cần cung cấp một số loại cấu hình cho mỗi lớp có thể được khởi tạo. Khai báo lớp như tĩnh không chỉ giúp tôi nhận thấy rằng nó sẽ không được khởi tạo mà còn tránh các nhà phát triển khác thêm thành viên thể hiện vào nó.
mdarefull

@florian leitride cách ưa thích sau đó là gì, một lớp chỉ có các phương thức tĩnh và / hoặc từ khóa trừu tượng? Điều đó có vẻ
nhảm nhí

180

Các lớp trừu tượng đã là công dân hạng nhất của TypeScript kể từ TypeScript 1.6. Bạn không thể khởi tạo một lớp trừu tượng.

Đây là một ví dụ:

export abstract class MyClass {         
    public static myProp = "Hello";

    public static doSomething(): string {
      return "World";
    }
}

const okay = MyClass.doSomething();

//const errors = new MyClass(); // Error

6
Khi làm việc với các lớp tĩnh, đây là phản hồi tốt nhất và tôi nêu lên điều này. Singletondành cho mẫu bộ nhớ dùng chung của cùng thể hiện của lớp. Ngoài ra, một lớp tĩnh không có cá thể theo định nghĩa, vì vậy bạn phải đưa ra một ngoại lệ nếu máy khách cố gắng khởi tạo nó.
loretoparisi

1
Chúng ta có thể làm một lớp trừu tượng?
KimchiMan

@KimchiMan - vâng, abstracttừ khóa được hỗ trợ trong TypeScript.
Fenton

1
Cập nhật để sử dụng abstract! @KimchiMan - ý tưởng tuyệt vời!
Obsidian

3
Đây có phải là một cách tiếp cận tốt hơn so với việc đánh dấu các nhà xây dựng là riêng tư?
Joro Tenev

72

Xác định các thuộc tính và phương thức tĩnh của một lớp được mô tả trong 8.2.1 của Đặc tả ngôn ngữ bản in :

class Point { 
  constructor(public x: number, public y: number) { } 
  public distance(p: Point) { 
    var dx = this.x - p.x; 
    var dy = this.y - p.y; 
    return Math.sqrt(dx * dx + dy * dy); 
  } 
  static origin = new Point(0, 0); 
  static distance(p1: Point, p2: Point) { 
    return p1.distance(p2); 
  } 
}

trong đó Point.distance()một phương thức tĩnh (hoặc "lớp").


14
Điều này cho thấy cách tạo một phương thức tĩnh , nó không trả lời câu hỏi về các lớp tĩnh (trừ khi câu hỏi thực sự thực sự là về các phương thức tĩnh).
Marcus

18
Cảm ơn các bình luận. Nó mô tả cách tạo các thuộc tính và phương thức tĩnh, được kết hợp với nhau cho phép một người tạo một lớp với dữ liệu và chức năng mà không cần phải khởi tạo. Mặc dù không đặc biệt là "lớp tĩnh", nhưng nó đáp ứng yêu cầu như được mô tả bởi ví dụ JavaScript của OP.
Rob Raisch

c # không có các lớp tĩnh cho đến phiên bản 2. chúng chỉ tồn tại trong c # để ngăn bạn khởi tạo chúng. bạn không thể làm điều đó với javascript để nó không có ý nghĩa nhiều
Simon_Weaver

4
@Simon_Weaver Bạn không thể làm gì trong javascript? Ngăn chặn các lớp học được khởi tạo? Bản in không quan tâm nhiều đến thời gian chạy, miễn là bạn gặp lỗi biên dịch khi bạn cố gắng làm những thứ bạn không được phép là tất cả những gì chúng ta cần.
Alex

Tôi đoán điều tôi muốn nói là 'chúng tôi không có TỪ KHÓA tĩnh ở cấp lớp (có nghĩa là bạn không thể tạo một thể hiện của một lớp)' cho đến phiên bản 2. nhưng đọc lại tôi nghĩ loại bình luận của tôi đã bỏ lỡ dù sao đi nữa. OP không thực sự tìm kiếm một "từ khóa tĩnh" cho cả lớp
Simon_Weaver

20

Câu hỏi này khá cũ nhưng tôi muốn để lại câu trả lời tận dụng phiên bản hiện tại của ngôn ngữ. Thật không may, các lớp tĩnh vẫn không tồn tại trong TypeScript tuy nhiên bạn có thể viết một lớp hoạt động tương tự chỉ với một chi phí nhỏ bằng cách sử dụng một hàm tạo riêng để ngăn việc tạo các lớp từ bên ngoài.

class MyStaticClass {
    public static readonly property: number = 42;
    public static myMethod(): void { /* ... */ }
    private constructor() { /* noop */ }
}

Đoạn mã này sẽ cho phép bạn sử dụng các lớp "tĩnh" tương tự như đối tác C # với nhược điểm duy nhất là vẫn có thể khởi tạo chúng từ bên trong. May mắn thay mặc dù bạn không thể mở rộng các lớp với các nhà xây dựng tư nhân.


9

Đây là một cách:

class SomeClass {
    private static myStaticVariable = "whatever";
    private static __static_ctor = (() => { /* do static constructor stuff :) */ })();
}

__static_ctorđây là một biểu thức hàm được gọi ngay lập tức Bản đánh máy sẽ xuất mã để gọi nó ở cuối lớp được tạo.

Cập nhật: Đối với các loại chung trong các hàm tạo tĩnh, không còn được phép tham chiếu bởi các thành viên tĩnh, bạn sẽ cần thêm một bước nữa:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private ___static_ctor = (() => { var someClass:SomeClass<T> ; /* do static constructor stuff :) */ })();
    private static __static_ctor = SomeClass.prototype.___static_ctor();
}

Trong mọi trường hợp, tất nhiên, bạn chỉ có thể gọi hàm tạo kiểu tĩnh chung sau lớp, chẳng hạn như:

class SomeClass<T> {
    static myStaticVariable = "whatever";
    private __static_ctor = (() => { var example: SomeClass<T>; /* do static constructor stuff :) */ })();
}
SomeClass.prototype.__static_ctor();

Chỉ cần nhớ KHÔNG BAO GIỜ sử dụng this__static_ctortrên (rõ ràng).


Cách này vẫn phát ra một hàm tạo cho lớp.
theycallmemorty

1
"Cách này" là một hack và không thay đổi hoạt động bình thường của máy tính, như mong đợi. Thậm chí class SomeClass {}tạo ra một nhà xây dựng - hầu như không đáng để bình luận về việc một vấn đề mới được đưa ra. ;) FYI: Không có "hàm tạo" thực sự nào trong JS - chỉ có các hàm có "cái này" khi được gọi trên các đối tượng hoặc thông qua new. Điều này tồn tại không có vấn đề gì đối với bất kỳ "lớp" nào.
James Wilkins

6

Tôi đã nhận được trường hợp sử dụng tương tự ngày hôm nay (31/07/2018) và thấy đây là một cách giải quyết. Nó dựa trên nghiên cứu của tôi và nó đã làm việc cho tôi. Mong đợi - Để đạt được những điều sau trong TypeScript:

var myStaticClass = {
    property: 10,
    method: function(){} 
}

Tôi đã làm điều này:

//MyStaticMembers.ts
namespace MyStaticMembers {
        class MyStaticClass {
           static property: number = 10;
           static myMethod() {...}
        }
        export function Property(): number {
           return MyStaticClass.property;
        }
        export function Method(): void {
           return MyStaticClass.myMethod();
        }
     }

Do đó chúng ta sẽ tiêu thụ nó như sau:

//app.ts
/// <reference path="MyStaticMembers.ts" />
    console.log(MyStaticMembers.Property);
    MyStaticMembers.Method();

Điều này làm việc cho tôi. Nếu bất cứ ai có đề nghị tốt hơn xin vui lòng cho tất cả chúng ta nghe nó !!! Cảm ơn...


3

Các lớp tĩnh trong các ngôn ngữ như C # tồn tại vì không có cấu trúc cấp cao nhất khác để nhóm dữ liệu và hàm. Tuy nhiên, trong JavaScript, họ làm như vậy và việc khai báo một đối tượng như bạn đã làm sẽ tự nhiên hơn nhiều. Để bắt chước chặt chẽ hơn cú pháp lớp, bạn có thể khai báo các phương thức như vậy:

const myStaticClass = {
    property: 10,

    method() {

    }
}

1
Không phải cách tiếp cận này làm rối loạn công việc của bạn? Một mặt bạn sử dụng các lớp và các thể hiện, và rồi đột nhiên bạn bắt đầu khai báo lại dữ liệu trong các đối tượng JS cũ thông thường ... Sử dụng các lớp tĩnh cũng hỗ trợ khả năng đọc, nó không chỉ là về hoạt động bên trong của ngôn ngữ.
Kokodoko

4
Tôi không thấy nó rối tung với quy trình làm việc của mình; ngược lại, tôi thấy rất thuận tiện khi sử dụng các đối tượng JavaScrpit không phân lớp hoặc chỉ các hàm và hằng đơn giản trong một mô-đun. Tuy nhiên, tôi cũng cố gắng tránh trạng thái toàn cầu càng nhiều càng tốt, vì vậy tôi hiếm khi có nhu cầu về một cái gì đó như các biến lớp tĩnh.
Yogu

1

Xem http://www.basarat.com/2013/04/typescript-static-constructor-for.html

Đây là một cách để 'giả' một nhà xây dựng tĩnh. Không phải là không có những nguy hiểm của nó - xem mục codeplex được tham chiếu .

class Test {
    static foo = "orig";

    // Non void static function
    static stat() {
        console.log("Do any static construction here");
        foo = "static initialized";
        // Required to make function non void
        return null;
    }
    // Static variable assignment
    static statrun = Test.stat();
}

// Static construction will have been done:
console.log(Test.foo);

1

Một cách có thể để đạt được điều này là có các thể hiện tĩnh của một lớp trong một lớp khác. Ví dụ:

class SystemParams
{
  pageWidth:  number = 8270;
  pageHeight: number = 11690;  
}

class DocLevelParams
{
  totalPages: number = 0;
}

class Wrapper
{ 
  static System: SystemParams = new SystemParams();
  static DocLevel: DocLevelParams = new DocLevelParams();
}

Sau đó, các tham số có thể được truy cập bằng Wrapper, mà không phải khai báo một thể hiện của nó. Ví dụ:

Wrapper.System.pageWidth = 1234;
Wrapper.DocLevel.totalPages = 10;

Vì vậy, bạn nhận được các lợi ích của đối tượng loại JavaScript (như được mô tả trong câu hỏi ban đầu) nhưng với các lợi ích của việc có thể thêm kiểu gõ TypeScript. Ngoài ra, nó tránh phải thêm 'tĩnh' trước tất cả các tham số trong lớp.


1

Với các mô-đun bên ngoài ES6, điều này có thể đạt được như vậy:

// privately scoped array
let arr = [];

export let ArrayModule = {
    add: x => arr.push(x),
    print: () => console.log(arr),
}

Điều này ngăn việc sử dụng các mô-đun và không gian tên nội bộ được coi là thực hành xấu của TSLint [1] [2] , cho phép phạm vi riêng tư và công khai và ngăn chặn việc khởi tạo các đối tượng lớp không mong muốn.


0

Tôi đã tìm kiếm một cái gì đó tương tự và đi qua một cái gì đó gọi là Singleton Pattern.

Tham khảo: Mẫu đơn

Tôi đang làm việc trên một lớp BulkLoader để tải các loại tệp khác nhau và muốn sử dụng mẫu Singleton cho nó. Bằng cách này, tôi có thể tải các tệp từ lớp ứng dụng chính của mình và truy xuất các tệp được tải dễ dàng từ các lớp khác.

Dưới đây là một ví dụ đơn giản về cách bạn có thể tạo trình quản lý điểm cho trò chơi với TypeScript và mẫu Singleton.

lớp SingletonClass {

private static _instance:SingletonClass = new SingletonClass();

private _score:number = 0;

constructor() {
    if(SingletonClass._instance){
        throw new Error("Error: Instantiation failed: Use SingletonDemo.getInstance() instead of new.");
    }
    SingletonClass._instance = this;
}

public static getInstance():SingletonClass
{
    return SingletonClass._instance;
}

public setScore(value:number):void
{
    this._score = value;
}

public getScore():number
{
    return this._score;
}

public addPoints(value:number):void
{
    this._score += value;
}

public removePoints(value:number):void
{
    this._score -= value;
}   }

Sau đó, bất cứ nơi nào trong các lớp học khác của bạn, bạn sẽ có quyền truy cập vào Singleton bằng cách:

var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10); scoreManager.addPoints(1);
scoreManager.removePoints(2); console.log( scoreManager.getScore() );

0

Bạn cũng có thể sử dụng từ khóa namespaceđể sắp xếp các biến, lớp, phương thức của mình, v.v. Xem tài liệu

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

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

Xem bình luận của Florian ở trên "Điều này không được chấp nhận. Ngoài ra, tslint sẽ không cho phép bạn làm điều đó nữa cho các mô-đun và không gian tên. Đọc ở đây: palantir.github.io/tslint/rules/no-namespace"
reggaeg Ức
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.