Kuinka muutan merkkijonon enumiksi TypeScriptissä?


Olen määritellyt seuraavan enumin TypeScriptissä:

enum Color{
    Red, Green
}

Nyt funktiossani vastaanotan värin merkkijonona. Olen kokeillut seuraavaa koodia:

var green= "Green";
var color : Color = <Color>green; // Error: can't convert string to enum

Kuinka voin muuntaa tuon arvon enum-tyypiksi?

Vastaus #1

TypeScript 0.9:n luettelot perustuvat merkkijonoihin ja numeroihin. Sinun ei pitäisi tarvita tyyppivahvistusta yksinkertaisiin muunnoksiin:

enum Color{
    Red, Green
}

// To String
 var green: string = Color[Color.Green];

// To Enum / number
var color : Color = Color[green];

Kokeile sitä verkossa

Minulla on dokumentaatiota tästä ja muista Enum-malleista OSS-kirjassani: https://basarat.gitbook.io/typescript/type-system/enums

Varmista, että käytät tätä, jos käytät TypeScript-lippua --noImplicitAny:

var color : Color = Color[green as keyof typeof Color];

Vastaus #2

Versiosta Typescript 2.1 lähtien merkkijonon avaimet enum-tyypeissä ovat vahvasti tyypitettyjä. keyof typeof-rakennetta käytetään saadakseen tietoa käytettävissä olevista merkkijonoavaimista (1):

enum Color{
    Red, Green
}

let typedColor: Color = Color.Green;
let typedColorString: keyof typeof Color = "Green";

// Error "Black is not assignable ..." (indexing using Color["Black"] will return undefined runtime)
typedColorString = "Black";

// Error "Type 'string' is not assignable ..." (indexing works runtime)
let letColorString = "Red";
typedColorString = letColorString;

// Works fine
typedColorString = "Red";

// Works fine
const constColorString = "Red";
typedColorString = constColorString

// Works fine (thanks @SergeyT)
let letColorString = "Red";
typedColorString = letColorString as keyof typeof Color;

typedColor = Color[typedColorString];

https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types

Vastaus #3

Jos annat merkkijonoarvoja enumillesi, suora tyyppimuunnos toimii aivan hyvin.

enum Color {
  Green = "Green",
  Red = "Red"
}

const color = "Green";
const colorEnum = color as Color;

Vastaus #4

enum Color{
    Red, Green
}

// To String
 var green: string = Color[Color.Green];

// To Enum / number
var color : Color = Color[green as keyof typeof Color]; //Works with --noImplicitAny

Tämä esimerkki toimii --noImplicitAny-asetuksen kanssa TypeScriptissä

Lähteet:
https://github.com/Microsoft/TypeScript/issues/13775#issuecomment-276381229 https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types

Vastaus #5

Koska käytät TypeScriptiä: Monet yllä olevista ratkaisuista eivät ehkä toimi tai ovat liian monimutkaisia.

Tilanne: Merkkijonot eivät ole samoja kuin enum-arvot (kirjainten koko eroaa)

enum Color {
  Green = "green",
  Red = "red"
}

Käytä vain:

const color = "green" as Color

Huomaa, että tämä ei takaa voimassa olevaa enumia.

Vastaus #6

Typescript 1.x

Jos olet varma, että syötesarja vastaa tarkalleen Color-enumin arvoa, käytä:

const color: Color = (<any>Color)["Red"];

Tapauksessa, jossa syötestrengi ei välttämättä vastaa Enumia, käytä:

const mayBeColor: Color | undefined = (<any>Color)["WrongInput"];
if (mayBeColor !== undefined){
     // TypeScript will understand that mayBeColor is of type Color here
}

Leikkikenttä


Jos emme tyypitä enum-tyyppiä <any>-tyypiksi, TypeScript näyttää virheen:

Elementillä on implisiittisesti 'any'-tyyppi, koska indeksiä ilmaiseva lauseke ei ole 'number'-tyyppiä.

Se tarkoittaa, että oletusarvoisesti TypeScriptin Enum-tyyppi toimii numeroindeksien kanssa, eli let c=Color[0], mutta ei merkkijonoindeksien kanssa, kuten let c=Color["string"]. Tämä on Microsoftin tiimin tiedossa oleva rajoitus laajemman ongelman Object string indexes yhteydessä.

Typescript 2.x-4.x

TypeScript siirtyi keyof typeof -käsitteeseen.

Jos joku käyttää merkkijonoarvoisia enum-arvoja:

enum Color {
  Green = "GRN",
  Red = "RD"
}

sitten on kieliratkaisu, jolla voi liittää avaimet arvoihin (Color.Green -> "GRN") vain pääsemällä enum-jäseneseen, mutta yksinkertaista tapaa tehdä päinvastaista ("GRN" -> Color.Green) ei ole. Käänteisestä kartoituksesta:

Muista, että merkkijonoenumin jäsenille ei luoda lainkaan käänteistä kartoitusta.

Mahdollinen ratkaisu on tarkistaa arvot manuaalisesti ja muuntaa arvo enum-tyyppiseksi. Huomaa, että se toimii vain merkkijono-enumeiden kanssa.

function enumFromStringValue<T> (enm: { [s: string]: T}, value: string): T | undefined {
  return (Object.values(enm) as unknown as string[]).includes(value)
    ? value as unknown as T
    : undefined;
}

enumFromStringValue(Color, "RD"); // Color.Red
enumFromStringValue(Color, "UNKNOWN"); // undefined
enumFromStringValue(Color, "Red"); // undefined

Vastaus #7

Yksinkertaisin lähestymistapa

enum Color { Red, Green }

const c1 = Color["Red"]
const redStr = "Red" // important: use `const`, not mutable `let`
const c2 = Color[redStr]

Tämä toimii sekä numeerisille että merkkijonopohjaisille enum-tyypeille. Tyypin varmistusta ei tarvitse käyttää.

Tuntemattomat enum-merkkijonot

const redStrWide: string = "Red" // wide, unspecific typed string
const c3 = Color[redStrWide as keyof typeof Color]
const isEnumName = <T>(str: string, _enum: T): str is Extract<keyof T, string> =>
    str in _enum
const enumFromName = <T>(name: string, _enum: T) => {
    if (!isEnumName(name, _enum)) throw Error() // here fail fast as an example
    return _enum[name]
}
const c4 = enumFromName(redStrWide, Color)

Muuta merkkijonoenumin arvot

Merkkijonojen luettelotyypeillä ei ole käänteistä kartoitusta (toisin kuin numeerisilla). Voimme luoda hakemistoapurin muuntaaksemme luettelotyypin arvon merkkijonoksi luettelotyypiksi:

enum ColorStr { Red = "red", Green = "green" }

const c5_by_name = ColorStr["Red"] // ✅ this works
const c5_by_value_error = ColorStr["red"] // ❌ , but this not

const enumFromValue = <T extends Record<string, string>>(val: string, _enum: T) => {
    const enumName = (Object.keys(_enum) as Array<keyof T>).find(k => _enum[k] === val)
    if (!enumName) throw Error() // here fail fast as an example
    return _enum[enumName]
}

const c5 = enumFromValue("red", ColorStr)

Leikkipuiston näyte

Vastaus #8

Sain sen toimimaan käyttämällä seuraavaa koodia.

var green= "Green";
var color : Color= <Color>Color[green];

Vastaus #9

Tämä huomautus liittyy basaratin vastaukseen, ei alkuperäiseen kysymykseen.

Minulla oli outo ongelma omassa projektissani, jossa kääntäjä antoi virheen, joka suunnilleen vastaa "ei voi muuttaa merkkijonoa Coloriksi", käyttäen tämän koodin vastaavaa versiota:

var colorId = myOtherObject.colorId; // value "Green";
var color: Color = <Color>Color[colorId]; // TSC error here: Cannot convert string to Color.

Huomasin, että kääntäjän tyypin päättely meni sekaisin ja se luuli, että colorId oli enum-arvo eikä ID. Ongelmasta selvitäkseni jouduin muuttamaan ID:n merkkijonoksi:

var colorId = <string>myOtherObject.colorId; // Force string value here
var color: Color = Color[colorId]; // Fixes lookup here.

En ole varma, mikä aiheutti ongelman, mutta jätän tämän muistiinpanon tänne siinä tapauksessa, että joku muu kohtaa saman ongelman kuin minä.

Vastaus #10

Jos TypeScript-kääntäjä tietää, että muuttujan tyyppi on merkkijono, tämä toimii:

let colorName : string = "Green";
let color : Color = Color[colorName];

Muuten sinun pitäisi nimenomaisesti muuntaa se merkkijonoksi (välttääksesi kääntäjän varoitukset):

let colorName : any = "Green";
let color : Color = Color["" + colorName];

Ajoaikana molemmat ratkaisut toimivat.

Vastaus #11

Minäkin törmäsin samaan kääntäjävirheeseen. Vain hieman lyhyempi versio Sly_cardinalin lähestymistavasta.

var color: Color = Color[<string>colorId];

Vastaus #12

Etsin vastausta, joka voi saada enum-arvon string-arvosta, mutta minun tapauksessani enum-arvoilla oli erilaiset merkkijonoarvot vastineina. OP:lla oli yksinkertainen enum Color-luokalle, mutta minulla oli jotain erilaista:

enum Gender {
  Male = 'Male',
  Female = 'Female',
  Other = 'Other',
  CantTell = "Can't tell"
}

Kun yrität ratkaista Gender.CantTell arvolla "Can't tell", se palauttaa undefined alkuperäisen vastauksen kanssa.

Toinen vastaus

Periaatteessa keksin toisen vastauksen, joka on voimakkaasti inspiroitunut tästä vastauksesta:

export const stringToEnumValue = <ET, T>(enumObj: ET, str: string): T =>
  (enumObj as any)[Object.keys(enumObj).filter(k => (enumObj as any)[k] === str)[0]];

Muistiinpanot

  • Otamme ensimmäisen tuloksen filter-funktiosta olettaen, että asiakas välittää kelvollisen merkkijonon enumista. Jos näin ei ole, palautetaan undefined.
  • Me muunnetaan enumObj tyyppiin any, koska TypeScript 3.0+:n kanssa (käytössä tällä hetkellä TypeScript 3.5), enumObj tulkitaan tyypiksi unknown.

Käyttöesimerkki

const cantTellStr = "Can't tell";

const cantTellEnumValue = stringToEnumValue<typeof Gender, Gender>(Gender, cantTellStr);
console.log(cantTellEnumValue); // Can't tell

Huomautus: Ja kuten joku huomautti kommentissa, halusin myös käyttää noImplicitAny.

Päivitetty versio

Ei tyyppimuunnosta any-tyyppiin ja asianmukaiset tyypitykset.

export const stringToEnumValue = <T, K extends keyof T>(enumObj: T, value: string): T[keyof T] | undefined =>
  enumObj[Object.keys(enumObj).filter((k) => enumObj[k as K].toString() === value)[0] as keyof typeof enumObj];

Myös päivitetty versio sisältää helpomman tavan kutsua sitä ja on helpommin luettavissa:

stringToEnumValue(Gender, "Can't tell");

Vastaus #13

Minun tarvitsi tietää, kuinka silmukoida enum-arvojen yli (testasin paljon useiden enumien permutaatioita) ja huomasin, että tämä toimi hyvin:

export enum Environment {
    Prod = "http://asdf.com",
    Stage = "http://asdf1234.com",
    Test = "http://asdfasdf.example.com"
}

Object.keys(Environment).forEach((environmentKeyValue) => {
    const env = Environment[environmentKeyValue as keyof typeof Environment]
    // env is now equivalent to Environment.Prod, Environment.Stage, or Environment.Test
}

Lähde: https://blog.mikeski.net/development/javascript/typescript-enums-to-from-string/

Vastaus #14

Yhteenvetona: Joko:

  • Tee funktio, joka jäsentää ja muuntaa merkkijonoarvon enum-arvoksi.
  • Jos tarvitset avaimen nimen annetun arvon perusteella, älä käytä TS-enumia.

Aluksi enum on kartoitus ihmisen luettavan nimen ja arvon välillä, tätä varten se on tehty.

Oletusarvot: TS varmistaa oletuksena, että sinulla on ainutlaatuinen arvo määritettyjen enum-avainien kohdalla.

Tämä

enum Color {
    Red, Green
}

Vastaava

enum Color {
    Red = 0,
    Green = 1
}

Molempien transpiloitu js-koodi tulee olemaan

"use strict";
var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
})(Color || (Color = {}));

Koska tämä on lukukelvoton, tässä on luotu objekti:

{0: 'Red', 1: 'Green', Red: 0, Green: 1}

Tällä objektilla on merkkijono- ja numerokenttiä (yhteen törmäämistä ei voi tapahtua, koska et voi määritellä enum-avainta numerona). TypeScript on tarpeeksi siisti luodakseen objektin, joka sisältää sekä mappauksen avain -> arvo että arvo -> avain.

Kiitos Jumalalle, tämä on bijektiivinen kuvaus, eli jokaisella ainutlaatuisella arvolla on oma ainutlaatuinen avaimensa (ja siksi päinvastainen on myös totta)

Nyt tulevat ongelmat, entä jos pakotan käyttämään samaa arvoa?

enum Color {
    Red = 0,
    Green = 0
}

Tämä on luotu js-objekti

{0: 'Green', Red: 0, Green: 0}

Meillä ei ole enää bijektiota, (tämä on surjektio), ei ole taikakartoitusta 0 : ['Green', 'Red']. Vain 0 : 'Green' ja menetimme 0 : 'Red'

Yhteenveto: TS yrittää aina asettaa käänteisen kartan (arvo -> avain), kun arvot ovat numeroita.

Nyt kuten ehkä tiedät, voit myös määritellä merkkijonojen arvoja enumissa, muutetaan vain Green-arvo "Green"iksi

enum Color {
    Red = 0,
    Green = "GREEN"
}

Tässä on tuloksena oleva js-objekti

{0: 'Red', Red: 0, Green: 'GREEN'}

Kuten näet, Typescript ei luo kartoitusarvo -> avain -suhdetta. Eikä se tee sitä, koska saatat päätyä tilanteeseen, jossa arvo ja avaimen nimi törmäävät. Muista: avain ei voi olla numero, joten kun arvo on numero, törmäysriskiä ei ole.

Tämä saa sinut ymmärtämään, ettet saisi luottaa enum-arvon -> avain -kartoitukseen. Kartoitus saattaa yksinkertaisesti olla olematon tai epätarkka.

Taas kerran, enum on, ja sitä tulisi pitää vain, ihmisluettavana nimenä arvolle. Joissakin tapauksissa TypeScript ei edes luo mitään käänteistä mappausta. Tämä on tilanne, kun määrittelet enum constin.

Const-enum on puhdas käännösaikainen enum, TS korvaa enumien käytön vastaavalla arvollaan käännöksen yhteydessä

Esimerkiksi:

const enum Color {
    Red = 0,
    Green = "GREEN"
}

Käännetään

"use strict";

Joten sanoakseni vain… mitään, koska "use strict"; ei liity edes siihen, mitä kirjoitimme.

Tässä on sama esimerkki käytön kanssa:

const enum Color {
    Red = 0,
    Green = "GREEN"
}
console.log(Color.Green);

Käännetään

"use strict";
console.log("GREEN" /* Green */);

Kuten näet, Color.Green korvataan transpilerin toimesta sanalla "GREEN" paikan päällä.

Joten takaisin alkuperäiseen kysymykseen, miten muunnetaan merkkijono enumiksi?

Jäsentäjän ratkaisu: Olen pahoillani, mutta ainoa siisti tapa, jota suosittelen, on kirjoittaa funktio, switch-case-rakenteen käyttö on nerokas tapa saavuttaa tämä.

function parseColorName(color: string): Color {
  switch (color) {
    case 'Red': return Color.Red;
    case 'Green': return Color.Green;
    default: throw new Error('unknown color');
  }
}

Räätälöity enum-ratkaisu:

Huomaa, että TypeScriptin enumit ovat läpinäkymättömiä, mikä tarkoittaa, ettei kääntäjä voi tyypittää arvoa oikein. Tästä syystä (ja erityisesti silloin, kun sinun täytyy käyttää käänteistä kartoitusta) suosittelisin tekemään oman enumisi seuraavasti:

export const ColorType = {
  RED: 'Red',
  GREEN: 'Green',
} as const;

export type ColorType = typeof ColorType[keyof typeof ColorType];

Seuraava on turvallista (color voi ottaa vain tunnetun kelvollisen arvon). Lyhyesti sanottuna, luotat merkkijonoyhtymiin enum-arvon sijaan.

const color: ColorType= "Green";
// And if you need to create a color from the enum like value:
const anotherColor: ColorType = ColorType.RED;

Aiheeseen liittyviä artikkeleita