Typecript có công đoàn, vậy enums có thừa không?


82

Kể từ khi TypeScript giới thiệu các kiểu liên kết, tôi tự hỏi liệu có lý do gì để khai báo một kiểu enum không. Hãy xem xét khai báo kiểu enum sau:

enum X { A, B, C }
var x:X = X.A;

và một khai báo kiểu liên minh tương tự:

type X: "A" | "B" | "C"
var x:X = "A";

Nếu về cơ bản chúng phục vụ cùng một mục đích, và các công đoàn mạnh mẽ hơn và có tính biểu cảm hơn, thì tại sao enums lại cần thiết?


1
Enums ánh xạ đến các con số, mà tôi đoán có thể hữu ích trong một số tình huống nhất định. Tôi giả định họ cũng muốn để cuối cùng làm nhiều hơn nữa với sự đếm, chẳng hạn như cho họ tính bạn có thể gọi số (như c # hoặc java enums.)
PaulBGD

Câu trả lời:


68

Theo như tôi thấy thì chúng không thừa, vì lý do rất đơn giản là các kiểu liên hợp hoàn toàn là một khái niệm thời gian biên dịch trong khi các enum thực sự được chuyển và kết thúc trong javascript ( mẫu ) kết quả .

Điều này cho phép bạn thực hiện một số việc với enum , điều này là không thể với các loại union (như liệt kê các giá trị enum có thể có )


1
Tôi nghĩ bạn muốn nói rằng có những lý do để sử dụng một enum. Câu đầu tiên là khó hiểu. "Theo như tôi thấy [enums] không phải ..." trả lời các câu hỏi "là [có] vì lý do nào để khai báo một kiểu enum."
matthew

1
trong bài đọc của tôi, câu đầu tiên của "Theo như tôi thấy thì không" đã đề cập đến enum trong câu hỏi tiêu đề "... vậy enums có thừa không?" . Tôi đang gửi một bản chỉnh sửa đến mức độ đó
manroe

49

Với các phiên bản gần đây của TypeScript, thật dễ dàng khai báo các kiểu liên hợp có thể lặp lại. Do đó, bạn nên thích các loại liên minh hơn enums.

Cách khai báo các kiểu liên hợp có thể lặp lại

const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'

// you can iterate over permissions
for (const permission of permissions) {
  // do something
}

Khi các giá trị thực tế của kiểu liên hợp không mô tả chính xác chúng, bạn có thể đặt tên chúng như cách bạn làm với enum.

// when you use enum
enum Permission {
  Read = 'r',
  Write = 'w',
  Execute = 'x'
}

// union type equivalent
const Permission = {
  Read: 'r',
  Write: 'w',
  Execute: 'x'
} as const;
type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x'

// of course it's quite easy to iterate over
for (const permission of Object.values(Permission)) {
  // do something
}

Đừng bỏ lỡ as constkhẳng định đóng vai trò quan trọng trong các mô hình này.

Tại sao nó không tốt để sử dụng enums?

1. Các enum non-const không phù hợp với khái niệm "một tập hợp JavaScript được đánh máy"

Tôi nghĩ rằng khái niệm này là một trong những lý do quan trọng khiến TypeScript trở nên phổ biến trong các ngôn ngữ altJS khác. Các enums không phải const vi phạm khái niệm bằng cách tạo ra các đối tượng JavaScript sống trong thời gian chạy với cú pháp không tương thích với JavaScript.

2. Const enums có một số cạm bẫy

Const enums không thể được chuyển đổi với Babel

Hiện tại có hai cách giải quyết cho vấn đề này: loại bỏ const enums theo cách thủ công hoặc bằng plugin babel-plugin-const-enum.

Khai báo const enums trong bối cảnh xung quanh có thể có vấn đề

Không cho phép sử dụng hằng số xung quanh khi --isolatedModulescờ được cung cấp. Một thành viên trong nhóm TypeScript nói rằng " const enumtrên DT thực sự không có ý nghĩa" (DT đề cập đến DefiniedlyTyped) và "Bạn nên sử dụng kiểu liên hợp của các ký tự (chuỗi hoặc số) thay vì" const enums trong ngữ cảnh xung quanh.

Const enums under --isolatedModulesflag hoạt động kỳ lạ ngay cả khi ở bên ngoài bối cảnh xung quanh

Tôi đã rất ngạc nhiên khi đọc nhận xét này trên GitHub và xác nhận rằng hành vi vẫn đúng với TypeScript 3.8.2.

3. Các ô số không phải là loại an toàn

Bạn có thể gán bất kỳ số nào cho enums dạng số.

enum ZeroOrOne {
  Zero = 0,
  One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!

4. Khai báo chuỗi enums có thể thừa

Đôi khi chúng ta thấy loại enums chuỗi này:

enum Day {
  Sunday = 'Sunday',
  Monday = 'Monday',
  Tuesday = 'Tuesday',
  Wednesday = 'Wednesday',
  Thursday = 'Thursday',
  Friday = 'Friday',
  Saturday = 'Saturday'
}

Tôi phải thừa nhận rằng có một tính năng enum mà các loại liên minh không đạt được

Ngay cả khi rõ ràng từ ngữ cảnh rằng giá trị chuỗi được bao gồm trong enum, bạn không thể gán nó cho enum.

enum StringEnum {
  Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!

Điều này thống nhất kiểu gán giá trị enum trong toàn bộ mã bằng cách loại bỏ việc sử dụng các giá trị chuỗi hoặc các ký tự chuỗi. Hành vi này không phù hợp với cách hệ thống kiểu TypeScript hoạt động ở những nơi khác và khá đáng ngạc nhiên và một số người nghĩ rằng điều này nên được khắc phục các vấn đề đã nêu ra ( điều nàyđiều này ), trong đó nó được đề cập nhiều lần rằng mục đích của chuỗi enums là để cung cấp các loại chuỗi "không rõ ràng": tức là chúng có thể được thay đổi mà không cần sửa đổi người tiêu dùng.

enum Weekend {
  Saturday = 'Saturday',
  Sunday = 'Sunday'
}
// As this style is forced, you can change the value of
// Weekend.Saturday to 'Sat' without modifying consumers
const weekend: Weekend = Weekend.Saturday;

Lưu ý rằng "độ mờ" này không hoàn hảo vì việc gán giá trị enum cho các kiểu chuỗi ký tự là không giới hạn.

enum Weekend {
  Saturday = 'Saturday',
  Sunday = 'Sunday'
}
// The change of the value of Weekend.Saturday to 'Sat'
// results in a compilation error
const saturday: 'Saturday' = Weekend.Saturday;

Nếu bạn nghĩ rằng tính năng "không rõ ràng" này có giá trị đến mức bạn có thể chấp nhận tất cả những nhược điểm mà tôi đã mô tả ở trên để đổi lấy nó, bạn không thể từ bỏ chuỗi enums.

Cách loại bỏ enums khỏi codebase của bạn

Với no-restricted-syntaxquy tắc của ESLint, như đã mô tả .


Được rồi. Nhưng nếu bạn muốn đặt khai báo này trong một tệp định dạng d.ts được chia sẻ thì sao? Bạn chỉ có thể khởi tạo một
hằng

1
Tôi không nghĩ rằng có thể lặp lại một cái gì đó chỉ được khai báo trong bối cảnh xung quanh. Cung cấp cả khai báo declare const permissions: readonly ['read', 'write', 'execute']và đối tượng JavaScript thực const permissions = ['read', 'write', 'execute']sẽ hoạt động.
kimamula

1
Tôi không muốn các ký tự chuỗi có thể gán được hoặc có thể so sánh với Enums. const foo: StringEnum === StringEnum.Foo // true or false Tôi muốn hạn chế này để đảm bảo rằng chúng ta không có hỗn hợp chuỗi theo nghĩa đen.
matthew

1
> Mẫu kiểu liên minh mát hơn nhiều @kimamula, đây là một lập luận yếu. Điều đó đến từ một người ủng hộ mô hình được đề xuất trong câu trả lời khác rất hay.
maninak

1
@maninak Cảm ơn bạn đã bình luận. Tôi đồng ý và loại bỏ phần đó.
kimamula

29

Có một số lý do bạn có thể muốn sử dụng enum

  • Bạn có thể lặp lại một enum.
  • Bạn có thể sử dụng một enumas flags. Cờ bit
  • Dưới đây là một số trường hợp sử dụng. Enums TypeScript Deep Dive.

Tôi thấy những lợi thế lớn của việc sử dụng liên hợp là chúng cung cấp một cách ngắn gọn để biểu diễn một giá trị với nhiều loại và chúng rất dễ đọc. let x: number | string

CHỈNH SỬA: Kể từ TypeScript 2.4 Enums hiện hỗ trợ chuỗi.

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
} 

5
Tôi có phải là người duy nhất nghĩ rằng sử dụng enumcho "cờ bit" là một mô hình chống lại không?
Cameron Tacklind

14

Về mặt khái niệm, Enums có thể được coi là một tập hợp con của các kiểu liên hợp, dành riêng cho intvà / hoặc stringgiá trị, với một vài tính năng bổ sung được đề cập trong các phản hồi khác giúp chúng thân thiện với việc sử dụng, ví dụ như không gian tên .

Về an toàn kiểu, enums số kém an toàn hơn, sau đó đến các kiểu liên hợp và cuối cùng là enums chuỗi:

// Numeric enum
enum Colors { Red, Green, Blue }
const c: Colors = 100; // ⚠️ No errors!

// Equivalent union types
type Color =
    | 0 | 'Red'
    | 1 | 'Green'
    | 2 | 'Blue';

let color: Color = 'Red'; // ✔️ No error because namespace free
color = 100; // ✔️ Error: Type '100' is not assignable to type 'Color'

type AltColor = 'Red' | 'Yellow' | 'Blue';

let altColor: AltColor = 'Red';
color = altColor; // ⚠️ No error because `altColor` type is here narrowed to `"Red"`

// String enum
enum NamedColors {
  Red   = 'Red',
  Green = 'Green',
  Blue  = 'Blue',
}

let namedColor: NamedColors = 'Red'; // ✔️ Error: Type '"Red"' is not assignable to type 'Colors'.

enum AltNamedColors {
  Red    = 'Red',
  Yellow = 'Yellow',
  Blue   = 'Blue',
}
namedColor = AltNamedColors.Red; // ✔️ Error: Type 'AltNamedColors.Red' is not assignable to type 'Colors'.

Tìm hiểu thêm về chủ đề đó trong bài viết 2ality này: TypeScript enums: Chúng hoạt động như thế nào? Chúng có thể được sử dụng để làm gì?


Các kiểu liên minh hỗ trợ dữ liệu và cấu trúc không đồng nhất, cho phép đa hình như:

class RGB {
    constructor(
        readonly r: number,
        readonly g: number,
        readonly b: number) { }

    toHSL() {
        return new HSL(0, 0, 0); // Fake formula
    }
}

class HSL {
    constructor(
        readonly h: number,
        readonly s: number,
        readonly l: number) { }

    lighten() {
        return new HSL(this.h, this.s, this.l + 10);
    }
}

function lightenColor(c: RGB | HSL) {
    return (c instanceof RGB ? c.toHSL() : c).lighten();
}

Giữa các kiểu enums và union, các singleton có thể thay thế các enum. Nó dài dòng hơn nhưng cũng hướng đối tượng hơn :

class Color {
    static readonly Red   = new Color(1, 'Red',   '#FF0000');
    static readonly Green = new Color(2, 'Green', '#00FF00');
    static readonly Blue  = new Color(3, 'Blue',  '#0000FF');

    static readonly All: readonly Color[] = [
        Color.Red,
        Color.Green,
        Color.Blue,
    ];

    private constructor(
        readonly id: number,
        readonly label: string,
        readonly hex: string) { }
}

const c = Color.Red;

const colorIds = Color.All.map(x => x.id);

Tôi có xu hướng nhìn vào F # để xem các phương pháp lập mô hình tốt. Trích dẫn từ một bài báo trên F # enums trên F # để mang lại niềm vui và lợi nhuận có thể hữu ích ở đây:

Nói chung, bạn nên thích các kiểu liên hợp phân biệt đối xử hơn là enums, trừ khi bạn thực sự cần có một int (hoặc a string) giá trị được liên kết với chúng

Có những lựa chọn thay thế khác cho mô hình enums. Một số trong số chúng được mô tả rõ ràng trong bài viết 2ality khác này Các lựa chọn thay thế cho enums trong TypeScript .


0

Kiểu enum không thừa, nhưng trong hầu hết các trường hợp, union được ưu tiên hơn.

Nhưng không phải lúc nào cũng vậy. Sử dụng enum để biểu diễn, ví dụ: chuyển đổi trạng thái có thể tiện dụng và biểu cảm hơn nhiều so với sử dụng union **

Xem xét kịch bản trực tiếp thực tế:

enum OperationStatus {
  NEW = 1,
  PROCESSING = 2,
  COMPLETED = 4
}

OperationStatus.PROCESSING > OperationStatus.NEW // true
OperationStatus.PROCESSING > OperationStatus.COMPLETED // false

2
Tôi nghĩ rằng nếu bạn làm điều này, bạn nên chỉ định rõ ràng các giá trị số trong khai báo enum để ngăn việc sắp xếp lại (ví dụ: vì ai đó nghĩ rằng sẽ có ý nghĩa hơn nếu xếp chúng theo thứ tự bảng chữ cái) khỏi việc tạo ra một lỗi thời gian chạy có thể bị bỏ qua.
dpoetzsch
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.