TypeScript-dekoratorer: Skriv renere & smartere kode nå!

TypeScript er et programmeringsspråk med sterk typing, som bygger videre på JavaScript og gir mer avanserte verktøy for større prosjekter. Det ble utviklet for å adressere noen av utfordringene som oppstår ved bruk av JavaScript. TypeScript løser fallgruvene i JavaScript gjennom bruken av eksplisitte typer.

I TypeScript har hver verdi en tilknyttet type. TypeScript kontrollerer at alle verdier samsvarer med reglene for sin type. Dette gjør at TypeScript kan oppdage feil i koden før programmet kjøres, også kalt statisk typekontroll. Det innebærer en tidlig feilsjekk i utviklingsprosessen basert på datatypene som brukes.

I tillegg til å fremme klarere og mer lesbar kode, og å levere statisk typekontroll, tilbyr TypeScript tilleggsfunksjoner som øker lesbarheten, gjenbrukbarheten og vedlikeholdbarheten. En slik funksjon er TypeScript-dekoratører.

TypeScript-dekoratører

Dekoratører i TypeScript er en mekanisme som tillater endring eller forbedring av atferden til kode ved kjøretid, eller å legge til metadata til koden. De muliggjør metaprogrammering, en teknikk der programmer kan behandle andre programmer som data, og dermed endre atferden deres.

Dekoratører er i bunn og grunn funksjoner som blir utført ved kjøretid når elementene de dekorerer åpnes eller endres. De tilfører ekstra funksjonalitet til de dekorerte elementene. TypeScript-dekoratører kan brukes på klassedeklarasjoner, metoder, egenskaper, aksessorer (gettere og settere) og metode-parametre.

I TypeScript innledes dekoratører med @-symbolet, og har formen @uttrykk der uttrykket evalueres til en funksjon som kalles ved kjøretid. Den generelle syntaksen for bruk av dekoratører er som følger:

@decoratorNavn
elementSomDekoreres

Her er et eksempel på en enkel klassedekoratør:

function logClass(target: Function) {
    console.log("Klassedekoratøren er kalt")
    console.log("Klasse:", target);
  }
  
  @logClass // @logClass er en dekoratør
  class MinKlasse {
    constructor() {
      console.log("En instans av MinKlasse er opprettet");
    }
  }
  
  const minInstans = new MinKlasse();

Resultatet av å kjøre koden over ser slik ut:

Utdata:

Klassedekoratøren er kalt
Klasse: [class MinKlasse]
En instans av MinKlasse er opprettet

Funksjonen `logClass()` tar et enkelt argument, `target` av typen `Function`. Argumentet `target` mottar konstruktøren til klassen som dekoreres. For å bruke `logClass()` som en dekoratør for klassen `MinKlasse` settes `@logClass` rett foran deklarasjonen. Dekoratøren må ha samme navn som funksjonen du ønsker å bruke for dekorering.

Når en instans av `MinKlasse` opprettes, blir dekoratørens kode utført i tillegg til klassens konstruktør, som demonstrert i utdataene.

Dekoratører er for tiden en eksperimentell funksjon i TypeScript. For å kunne bruke dem, må `experimentalDecorators` aktiveres i `tsconfig.json`-filen. For å gjøre dette, genereres en `tsconfig.json`-fil i prosjektmappen ved hjelp av følgende kommando i terminalen i prosjektkatalogen:

tsc --init

Når `tsconfig.json`-filen er opprettet, åpnes den, og kommentaren foran `experimentalDecorators` fjernes slik som vist nedenfor:

I tillegg må JavaScript målversjonen settes til minst ES2015.

Betydningen av TypeScript-dekoratører

God kode kjennetegnes av lesbarhet, gjenbrukbarhet og vedlikeholdbarhet. Lesbar kode er lett å forstå, tolke og formidler utviklerens intensjon tydelig. Gjenbrukbar kode betyr at spesifikke komponenter, som funksjoner og klasser, kan gjenbrukes i andre deler av applikasjonen eller i en ny applikasjon uten store endringer. Vedlikeholdbar kode er enkel å endre, oppdatere og feilrette gjennom livssyklusen.

TypeScript-dekoratører bidrar til lesbarhet, gjenbrukbarhet og vedlikeholdbarhet. For det første, lar de deg forbedre koden ved bruk av en deklarativ syntaks som er lett å lese. Logikken kan innkapsles i dekoratører og brukes ved å dekorere forskjellige elementer i koden. Dette forenkler koden og gjør det tydelig hva som skjer. Dekoratører kommuniserer utviklerens hensikt.

Dekoratører er ikke engangskomponenter, de er i utgangspunktet gjenbrukbare. De kan defineres en gang og brukes mange ganger på flere områder. Du kan importere dekoratører og bruke dem hvor som helst i kodebasen der du vil endre kodeatferden. Dette reduserer duplisering av logikk, og forbedrer gjenbrukbarheten.

Dekoratører gir også fleksibilitet og modularitet, slik at forskjellig funksjonalitet kan skilles i uavhengige komponenter. Kombinert med det faktum at de tillater skriving av lesbar og gjenbrukbar kode, betyr det at TypeScript-dekoratører gjør det enklere å vedlikeholde koden.

Typer TypeScript-dekoratører

TypeScript-dekoratører kan knyttes til klasser, klasseegenskaper, klassemetoder, klasseaksessorer og klassemetodeparametre. Ut ifra elementene som kan dekoreres får vi de forskjellige typene dekoratører, som inkluderer:

#1. Klasse dekoratør

En klassedekoratør brukes til å observere, endre eller erstatte en klassedefinisjon. Den deklareres rett før klassen den skal dekorere. Klassedekoratøren brukes på konstruktøren av klassen. Ved kjøretid vil klassedekoratøren kalles med konstruktøren av klassen som det eneste argumentet.

Nedenfor vises en klassedekoratør som brukes til å forhindre at en klasse utvides:

function frozen(target: Function) {
    Object.freeze(target);
    Object.freeze(target.prototype)
  }

  @frozen
  class Vehicle {
    wheels: number = 4;
    constructor() {
      console.log("Et kjøretøy er opprettet")
    }
  }

  class Car extends Vehicle {
    constructor() {
      super();
      console.log("En bil er opprettet");
    }
  }

  console.log(Object.isFrozen(Vehicle));

For å forhindre at en klasse utvides, brukes `Object.freeze()` på klassen. En dekoratør brukes til å legge til denne funksjonaliteten. Vi kan sjekke om klassen `Vehicle` er frosset ved å sende klassen til `isFrozen()`. Resultatet av koden er:

true

#2. Egenskapsdekoratør

En egenskapsdekoratør brukes til å dekorere en klasseegenskap, og den deklareres rett før egenskapens deklarasjon. Egenskapsdekoratører kan brukes til å endre eller observere en egenskapsdefinisjon.

Ved kjøretid vil dekoratøren bli kalt med to argumenter: enten konstruktørfunksjonen til klassen hvis medlemmet er statisk, eller prototypen til klassen hvis det er et instansmedlem. Det andre argumentet er navnet på medlemmet.

I TypeScript har statiske medlemmer nøkkelordet `static`. Statiske medlemmer kan nås uten å opprette en instans av klassen. Instansmedlemmer har ikke `static` og kan kun nås etter at en instans av klassen er opprettet.

Et eksempel på en egenskapsdekoratør er:

function wheelsDecorator(target: any, propertyName: string) {
    console.log(propertyName.toUpperCase())
  }

  class Vehicle {
    @wheelsDecorator
    wheels: number = 4;
    constructor() {
      console.log("Et kjøretøy er opprettet")
    }
  }

Resultatet av å kjøre koden er:

WHEELS

#3. Metode dekoratør

En metodedekoratør, deklarert rett før en metode, brukes til å observere, endre eller erstatte en metodedefinisjon. Den tar tre argumenter: konstruktørfunksjonen til klassen hvis medlemmet er statisk, eller prototypen til klassen hvis det er et instansmedlem.

Det andre argumentet er navnet på medlemmet, og til slutt en egenskapsbeskrivelse. En egenskapsbeskrivelse er et objekt knyttet til objekters egenskaper som gir informasjon om en egenskap sitt attributt og oppførsel.

Metodedekoratører er nyttige når man ønsker å utføre en handling før eller etter at en metode kalles. De kan også brukes til å logge informasjon om metoden som kalles, for eksempel hvis en metode er utdatert, men fortsatt tilgjengelig for bruk.

Et eksempel på en metodedekoratør er:

const logDeprecated =(target: any, methodName: string, descriptor: PropertyDescriptor) => {
    console.log(`${methodName} er utdatert`)
    console.log(descriptor);
  }
  
  class Vehicle {
    wheels: number = 4;
    constructor() {
      console.log("Et kjøretøy er opprettet")
    }
    @logDeprecated
    reFuel(): void {
      console.log("Kjøretøyet fylles");
    }
  }

Resultat:

reFuel er utdatert
  {
    value: [Function: reFuel],
    writable: true,
    enumerable: false,
    configurable: true
  }

#4. Tilbehørs dekoratører

I TypeScript finnes det to typer tilgangsmetoder, get og set. Aksessormetoder brukes til å kontrollere tilgangen til klasseegenskaper. Tilbehørsdekoratører brukes til å dekorere disse metodene, og de deklareres rett før en tilbehørsdeklarasjon. Siden tilbehør fortsatt er metoder, fungerer tilbehørsdekoratører akkurat som metodedekoratører.

Et eksempel på tilbehørsdekoratører er:

const logWheels =(target: any, accessorName: string, descriptor: PropertyDescriptor) => {
    console.log(`${accessorName} brukes for å hente antall hjul`)
    console.log(descriptor);
  }
  
  class Vehicle {
    private wheels: number = 4;
    constructor() {
      console.log("Et kjøretøy er opprettet")
    }
    @logWheels
    get numWheels(): number {
      return this.wheels;
    }
  }

Resultat:

numWheels brukes for å hente antall hjul
  {
    get: [Function: get numWheels],
    set: undefined,
    enumerable: false,
    configurable: true
  }

Ved bruk av tilbehørsdekoratører er det viktig å merke seg at dekoratører ikke kan brukes på flere get/set-tilbehør med samme navn. For eksempel kan ikke `logWheels`-dekoratøren brukes på en set-metode med samme navn som `get numWheels` i eksempelet over.

#5. Parameterdekoratører

En parameterdekoratør brukes til å observere at en parameter er deklarert i en metode. Den deklareres før en parameterdeklarasjon. Parameterdekoratører tar tre argumenter: konstruktørfunksjonen for et statisk medlem, eller prototypen til klassen for et instansmedlem.

Det andre argumentet er navnet på medlemmet, det vil si parameternavnet. Det tredje argumentet er den ordinale indeksen til parameteren i funksjonens parameterliste. Den første parameteren har indeks 0.

Et eksempel på en parameterdekoratør er:

const passengerLog = (target: Object, propertyKey: string, parameterIndex: number) => {
    console.log(`Dekoratør på ${propertyKey} sin parameter med indeks ${parameterIndex}`);
  }
  
  class Vehicle {
    private wheels: number = 4;
    constructor() {
      console.log("Et kjøretøy er opprettet")
    }
    pickPassenger( location: string, numPassengers: string, @passengerLog driver: string) {
      console.log(`${numPassengers} plukket opp på ${location} av ${driver}`)
    }
    dropPassenger(driver: string, @passengerLog location: string, numPassengers: string) {
      console.log(`${numPassengers} sluppet av på ${location} av ${driver}`)
    }
  }

Resultat:

Dekoratør på pickPassenger sin parameter med indeks 2
Dekoratør på dropPassenger sin parameter med indeks 1

Konklusjon

TypeScript-dekoratører bidrar til å forbedre lesbarheten i koden og hjelpe deg med å skrive modulær, gjenbrukbar kode, siden dekoratører kan deklareres en gang og brukes mange ganger. Dekoratører øker også vedlikeholdsvennligheten til koden.

Selv om de fortsatt er en eksperimentell funksjon, er dekoratører svært nyttige, og du bør definitivt vurdere å gjøre deg kjent med dem.

Du kan også lese hvordan du konverterer en streng til et tall i TypeScript.