admin 管理员组文章数量: 1086019
I wanted to create an enum that would map strings that I receive from the backend into something we can consistently use across the frontend. Let's say it's something like:
enum KEY_ENUM {
DENY = "RESTRICTED_ACCESS",
ACCES = "ACCES_WITH_PRIVILEGE",
}
So far so good. The problem came when I needed to order the strings. To do so I created a tuple ordering the values:
const ORDER_OF_PRIVILEGES = [KEY_ENUM.ACCES, KEY_ENUM.DENY] as const
And then I wanted to take an array of objects that I would get from the backend and sort them by the level of privilege:
type KeyEnumValues = `${KEY_ENUM}`
type User = {
id: number,
privilege: KeyEnumValues
}
const usersToCheck = new Array<User>
const usersSortedByPrivilege = [...usersToCheck].sort((a, b)=>{
return ORDER_OF_PRIVILEGES.indexOf(a.privilege) > ORDER_OF_PRIVILEGES.indexOf(b.privilege) ? 1 : -1
})
Unfortunately, this results in an error:
Argument of type '"RESTRICTED_ACCESS" | "ACCES_WITH_PRIVILEGE"' is not assignable to parameter of type 'KEY_ENUM'.
Type '"RESTRICTED_ACCESS"' is not assignable to type 'KEY_ENUM'.
What am I missing here? The same operation with an object assigned as const instead of an enum works correctly. Is it even possible to create an ordered list of enums?
I wanted to create an enum that would map strings that I receive from the backend into something we can consistently use across the frontend. Let's say it's something like:
enum KEY_ENUM {
DENY = "RESTRICTED_ACCESS",
ACCES = "ACCES_WITH_PRIVILEGE",
}
So far so good. The problem came when I needed to order the strings. To do so I created a tuple ordering the values:
const ORDER_OF_PRIVILEGES = [KEY_ENUM.ACCES, KEY_ENUM.DENY] as const
And then I wanted to take an array of objects that I would get from the backend and sort them by the level of privilege:
type KeyEnumValues = `${KEY_ENUM}`
type User = {
id: number,
privilege: KeyEnumValues
}
const usersToCheck = new Array<User>
const usersSortedByPrivilege = [...usersToCheck].sort((a, b)=>{
return ORDER_OF_PRIVILEGES.indexOf(a.privilege) > ORDER_OF_PRIVILEGES.indexOf(b.privilege) ? 1 : -1
})
Unfortunately, this results in an error:
Argument of type '"RESTRICTED_ACCESS" | "ACCES_WITH_PRIVILEGE"' is not assignable to parameter of type 'KEY_ENUM'.
Type '"RESTRICTED_ACCESS"' is not assignable to type 'KEY_ENUM'.
What am I missing here? The same operation with an object assigned as const instead of an enum works correctly. Is it even possible to create an ordered list of enums?
Share Improve this question edited Mar 27 at 12:40 jonrsharpe 122k30 gold badges268 silver badges475 bronze badges asked Mar 27 at 12:33 Michał SadowskiMichał Sadowski 2,1791 gold badge13 silver badges26 bronze badges 3 |1 Answer
Reset to default 1The main intended use case for enum
s is to treat them as nominal types so that you don't accidentally mix them up. For example, the following is intentionally an error in TypeScript:
enum Attribute {
DEXTERITY = "DEX",
CONSTITUTION = "CON"
}
enum Device {
PRINTER = "PRN",
CONSOLE = "CON"
}
const device: Device = Attribute.CONSTITUTION;
Here it is considered to be a mistake to allow assigning an Attribute
where a Device
is expected, even though at runtime it's just "CON"
, which is a valid string value for both enums. The intent of enum
s is to treat the values as "opaque" things and you should be dealing mostly with them by key. See this comment on microsoft/TypeScript#17690 for more information.
So "RESTRICTED_ACCESS" | "ACCES_WITH_PRIVILEGE"
is intentionally not assignable to the KEY_ENUM
type.
If you really care about the string values, it's an indication that maybe you really want to use a const
-asserted object instead of an enum
, as you mentioned.
Still, you can access the string literal types of the enums, using template literal types to serialize them, as you've done in KeyEnumValues
. And while KeyEnumValues
isn't not assignable to KEY_ENUM
, the reverse direction is assignable. That is, KEY_ENUM
is assignable to KeyEnumValues
(you can think of it like KEY_ENUM.DENY
is a special nominal subtype of "RESTRICTED_ACCESS"). So KeyEnumValues
is wider than KEY_ENUM
.
So then, if every KEY_ENUM
is assignable to KeyEnumValues
, why can't you search for a KeyEnumValues
element inside an array of KEY_ENUM
with indexOf()
?
That's because the TypeScript call signature type for indexOf()
is:
interface ReadonlyArray<T> {
indexOf(searchElement: T, fromIndex?: number): number;
}
And so you can only search for something narrower than the type of the array elements, not wider. But conceptually this is backwards from what people tend to want. There have been many GitHub issues opened on this topic: for example, see microsoft/TypeScript#54422.
And the underlying reason why this can't easily be done is that while it's easy to constrain generic types to be narrower than something, there's no native way to constrain them to be wider. That is, TypeScript lacks so-called lower bound generic constraints, as requested in microsoft/TypeScript#14520 (the issue most of the above issues get closed as duplicating).
If TypeScript had lower bound constraints (usually written as super
instead of extends
as in Java), then indexOf()
could be typed as
interface ReadonlyArray<T> {
indexOf<S super T>(searchElement: S, fromIndex?: number): number;
}
and then your call would succeed. But it doesn't, so the call fails. There are ways to try to force TypeScript to allow these calls, but the easiest thing to do is just widen the array type before calling indexOf()
:
const OOP: readonly KeyEnumValues[] = ORDER_OF_PRIVILEGES; // okay
const usersSortedByPrivilege = [...usersToCheck].sort((a, b) => {
return OOP.indexOf(a.privilege) > OOP.indexOf(b.privilege) ? 1 : -1
})
Now T
is just KeyEnumValues
and indexOf()
succeeds. And the widening of ORDER_OF_PRIVILEGES
to readonly KeyEnumValues[]
works because KeyEnumValues
is itself wider than KEY_ENUM
.
See Why does the argument for Array.prototype.includes(searchElement) need the same type as array elements? for a very similar question and answer involving includes()
, which has the same issue.
Playground link to code
本文标签: typescriptTuple ordering elements of stringkeyed enumStack Overflow
版权声明:本文标题:typescript - Tuple ordering elements of string-keyed enum - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://roclinux.cn/p/1744089162a2531703.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
as const
object if that's your use case. (github/microsoft/TypeScript/issues/17690) • Still, if you want to do that, you should widen your array to that string literal type before checking withindexOf
, possibly as shown in this playground link. Does that fully address the q? If so I'll write an a or find a duplicate; if not, what am I missing? – jcalz Commented Mar 27 at 13:33