Vấn đề của enum và các giải pháp thay thế
Kiểu liệt kê (enum) trong TypeScript có một số vấn đề được chỉ ra. Ở đây sẽ giải thích các vấn đề đó và các giải pháp thay thế.
Vấn đề của enum
Enum quá đặc thù của TypeScript
TypeScript là ngôn ngữ mở rộng từ JavaScript. Tuy là mở rộng, nhưng không phải thêm tính năng bừa bãi, mà chỉ giới hạn trong thế giới type. Do tư tưởng này của TypeScript, nếu bỏ qua phần liên quan đến type thì ngôn ngữ không quá xa rời cú pháp JavaScript.
Cũng có những AltJS xa rời cú pháp JavaScript một cách triệt để. Trong số đó, TypeScript được nhiều developer ủng hộ một phần vì sự hấp dẫn của việc không xa rời JavaScript quá nhiều.
Nhìn vào enum của TypeScript, không chỉ cú pháp không có trong JavaScript, mà enum sau khi compile còn biến thành object của JavaScript, đây là tính năng riêng vượt ra ngoài việc mở rộng thế giới type. Một số TypeScript programmer không thể chấp nhận điểm này.
Enum số có vấn đề về type safety
Enum số có vấn đề về type safety là có thể gán bất kỳ giá trị number nào. Ví dụ sau là enum chỉ có member với giá trị 0 và 1, nhưng thực tế có thể gán các số khác.
Vấn đề này xảy ra với TypeScript phiên bản dưới 5.0.
ts// TypeScript v4.9.5enumZeroOrOne {Zero = 0,One = 1,}constzeroOrOne :ZeroOrOne = 9; // Không có compile error!
ts// TypeScript v4.9.5enumZeroOrOne {Zero = 0,One = 1,}constzeroOrOne :ZeroOrOne = 9; // Không có compile error!
Từ TypeScript 5.0 đã được cải thiện và sẽ phát sinh compile error.
ts// TypeScript v5.0.4enumZeroOrOne {Zero = 0,One = 1,}constType '9' is not assignable to type 'ZeroOrOne'.2322Type '9' is not assignable to type 'ZeroOrOne'.: zeroOrOne ZeroOrOne = 9;
ts// TypeScript v5.0.4enumZeroOrOne {Zero = 0,One = 1,}constType '9' is not assignable to type 'ZeroOrOne'.2322Type '9' is not assignable to type 'ZeroOrOne'.: zeroOrOne ZeroOrOne = 9;
Enum có đặc tính là khi truy cập object enum bằng giá trị sẽ nhận được tên member. Khi truy cập bằng giá trị không có trong member, ta mong muốn có compile error, nhưng không được như vậy.
tsenumZeroOrOne {Zero = 0,One = 1,}console .log (ZeroOrOne [0]); // Đây là như mong đợiconsole .log (ZeroOrOne [9]); // Đây mong muốn có compile error...
tsenumZeroOrOne {Zero = 0,One = 1,}console .log (ZeroOrOne [0]); // Đây là như mong đợiconsole .log (ZeroOrOne [9]); // Đây mong muốn có compile error...
Chỉ enum chuỗi là nominal type
Hệ thống type của TypeScript áp dụng structural subtyping. Tuy nhiên, enum chuỗi là ngoại lệ và là nominal type.
tsenumStringEnum {Foo = "foo",}constfoo1 :StringEnum =StringEnum .Foo ; // Compile thành côngconstType '"foo"' is not assignable to type 'StringEnum'.2322Type '"foo"' is not assignable to type 'StringEnum'.: foo2 StringEnum = "foo"; // Compile error
tsenumStringEnum {Foo = "foo",}constfoo1 :StringEnum =StringEnum .Foo ; // Compile thành côngconstType '"foo"' is not assignable to type 'StringEnum'.2322Type '"foo"' is not assignable to type 'StringEnum'.: foo2 StringEnum = "foo"; // Compile error
Đặc tính này gây bất ngờ. Hơn nữa, enum số không phải nominal type nên có sự không nhất quán.
Các giải pháp thay thế cho enum
Dưới đây là một số giải pháp thay thế cho enum. Tuy nhiên, không có giải pháp nào tái hiện 100% đặc điểm của enum. Hãy cân nhắc mục đích và công dụng khi sử dụng các giải pháp sau.
Giải pháp thay thế 1: Union type
Giải pháp đơn giản nhất là sử dụng union type.
tstypeYesNo = "yes" | "no";functiontoVietnamese (yesno :YesNo ) {switch (yesno ) {case "yes":return "Có";case "no":return "Không";}}
tstypeYesNo = "yes" | "no";functiontoVietnamese (yesno :YesNo ) {switch (yesno ) {case "yes":return "Có";case "no":return "Không";}}
Cũng có thể kết hợp union type với Symbol.
tsconstyes =Symbol ();constno =Symbol ();typeYesNo = typeofyes | typeofno ;functiontoVietnamese (yesno :YesNo ) {switch (yesno ) {caseyes :return "Có";caseno :return "Không";}}
tsconstyes =Symbol ();constno =Symbol ();typeYesNo = typeofyes | typeofno ;functiontoVietnamese (yesno :YesNo ) {switch (yesno ) {caseyes :return "Có";caseno :return "Không";}}
Giải pháp thay thế 2: Object literal
Cũng có thể sử dụng object literal.
tsconstPosition = {Top : 0,Right : 1,Bottom : 2,Left : 3,} asconst ;typePosition = (typeofPosition )[keyof typeofPosition ];// Dòng trên có nghĩa tương đương với type Position = 0 | 1 | 2 | 3functiontoVietnamese (position :Position ) {switch (position ) {casePosition .Top :return "Trên";casePosition .Right :return "Phải";casePosition .Bottom :return "Dưới";casePosition .Left :return "Trái";}}
tsconstPosition = {Top : 0,Right : 1,Bottom : 2,Left : 3,} asconst ;typePosition = (typeofPosition )[keyof typeofPosition ];// Dòng trên có nghĩa tương đương với type Position = 0 | 1 | 2 | 3functiontoVietnamese (position :Position ) {switch (position ) {casePosition .Top :return "Trên";casePosition .Right :return "Phải";casePosition .Bottom :return "Dưới";casePosition .Left :return "Trái";}}
Tổng kết
Đã giải thích các vấn đề của enum và các giải pháp thay thế. Đặc biệt vì enum có vấn đề về type safety, hãy cân nhắc kỹ trước khi sử dụng tích cực.