Svenska

Använd TypeScript readonly-typer för att skapa oföränderliga datastrukturer. Bygg förutsägbara och robusta applikationer genom att förhindra oavsiktliga mutationer.

TypeScript Readonly-typer: Bemästra oföränderliga datastrukturer

I det ständigt föränderliga landskapet för mjukvaruutveckling är strävan efter robust, förutsägbar och underhållbar kod en konstant ansträngning. TypeScript, med sitt starka typsystem, erbjuder kraftfulla verktyg för att uppnå dessa mål. Bland dessa verktyg utmärker sig readonly-typer som en avgörande mekanism för att upprätthålla oföränderlighet, en hörnsten i funktionell programmering och en nyckel till att bygga mer tillförlitliga applikationer.

Vad är oföränderlighet och varför är det viktigt?

Oföränderlighet, i sin kärna, innebär att när ett objekt har skapats kan dess tillstånd inte ändras. Detta enkla koncept har djupgående konsekvenser för kodkvalitet och underhållbarhet.

Readonly-typer i TypeScript: Din arsenal för oföränderlighet

TypeScript erbjuder flera sätt att upprätthålla oföränderlighet med hjälp av nyckelordet readonly. Låt oss utforska de olika teknikerna och hur de kan tillämpas i praktiken.

1. Readonly-egenskaper på interfaces och typer

Det mest direkta sättet att deklarera en egenskap som readonly är att använda nyckelordet readonly direkt i en interface- eller typdefinition.


interface Person {
  readonly id: string;
  name: string;
  age: number;
}

const person: Person = {
  id: "unique-id-123",
  name: "Alice",
  age: 30,
};

// person.id = "new-id"; // Fel: Kan inte tilldela till 'id' eftersom det är en skrivskyddad egenskap.
person.name = "Bob"; // Detta är tillåtet

I detta exempel deklareras egenskapen id som readonly. TypeScript kommer att förhindra alla försök att ändra den efter att objektet har skapats. Egenskaperna name och age, som saknar readonly-modifieraren, kan ändras fritt.

2. Hjälptypen Readonly

TypeScript erbjuder en kraftfull hjälptyp som kallas Readonly<T>. Denna generiska typ tar en befintlig typ T och omvandlar den genom att göra alla dess egenskaper readonly.


interface Point {
  x: number;
  y: number;
}

const point: Readonly<Point> = {
  x: 10,
  y: 20,
};

// point.x = 30; // Fel: Kan inte tilldela till 'x' eftersom det är en skrivskyddad egenskap.

Typen Readonly<Point> skapar en ny typ där både x och y är readonly. Detta är ett bekvämt sätt att snabbt göra en befintlig typ oföränderlig.

3. Skrivskyddade arrayer (ReadonlyArray<T>) och readonly T[]

Arrayer i JavaScript är i grunden föränderliga. TypeScript erbjuder ett sätt att skapa skrivskyddade arrayer med hjälp av typen ReadonlyArray<T> eller kortformen readonly T[]. Detta förhindrar modifiering av arrayens innehåll.


const numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
// numbers.push(6); // Fel: Egenskapen 'push' existerar inte på typen 'readonly number[]'.
// numbers[0] = 10; // Fel: Indexsignaturen i typen 'readonly number[]' tillåter endast läsning.

const moreNumbers: readonly number[] = [6, 7, 8, 9, 10]; // Motsvarar ReadonlyArray
// moreNumbers.push(11); // Fel: Egenskapen 'push' existerar inte på typen 'readonly number[]'.

Försök att använda metoder som ändrar arrayen, såsom push, pop, splice, eller att direkt tilldela ett index, kommer att resultera i ett TypeScript-fel.

4. const kontra readonly: Förstå skillnaden

Det är viktigt att skilja mellan const och readonly. const förhindrar omtilldelning av själva variabeln, medan readonly förhindrar modifiering av objektets egenskaper. De tjänar olika syften och kan användas tillsammans för maximal oföränderlighet.


const immutableNumber = 42;
// immutableNumber = 43; // Fel: Kan inte tilldela om const-variabeln 'immutableNumber'.

const mutableObject = { value: 10 };
mutableObject.value = 20; // Detta är tillåtet eftersom *objektet* inte är const, bara variabeln.

const readonlyObject: Readonly<{ value: number }> = { value: 30 };
// readonlyObject.value = 40; // Fel: Kan inte tilldela till 'value' eftersom det är en skrivskyddad egenskap.

const constReadonlyObject: Readonly<{ value: number }> = { value: 50 };
// constReadonlyObject = { value: 60 }; // Fel: Kan inte tilldela om const-variabeln 'constReadonlyObject'.
// constReadonlyObject.value = 60; // Fel: Kan inte tilldela till 'value' eftersom det är en skrivskyddad egenskap.

Som demonstrerats ovan säkerställer const att variabeln alltid pekar på samma objekt i minnet, medan readonly garanterar att objektets interna tillstånd förblir oförändrat.

Praktiska exempel: Använda Readonly-typer i verkliga scenarier

Låt oss utforska några praktiska exempel på hur readonly-typer kan användas för att förbättra kodkvalitet och underhållbarhet i olika scenarier.

1. Hantera konfigurationsdata

Konfigurationsdata laddas ofta en gång vid applikationens start och bör inte ändras under körning. Att använda readonly-typer säkerställer att denna data förblir konsekvent och förhindrar oavsiktliga ändringar.


interface AppConfig {
  readonly apiUrl: string;
  readonly timeout: number;
  readonly features: readonly string[];
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  features: ["featureA", "featureB"],
};

function fetchData(url: string, config: Readonly<AppConfig>) {
    // ... använd config.timeout och config.apiUrl säkert, i vetskap om att de inte kommer att ändras
}

fetchData("/data", config);

2. Implementera Redux-liknande state-hantering

I state-hanteringsbibliotek som Redux är oföränderlighet en kärnprincip. Readonly-typer kan användas för att säkerställa att state förblir oföränderligt och att reducers endast returnerar nya state-objekt istället för att modifiera de befintliga.


interface State {
  readonly count: number;
  readonly items: readonly string[];
}

const initialState: State = {
  count: 0,
  items: [],
};

function reducer(state: Readonly<State>, action: { type: string; payload?: any }): State {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, count: state.count + 1 }; // Returnera ett nytt state-objekt
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] }; // Returnera ett nytt state-objekt med uppdaterade items
    default:
      return state;
  }
}

3. Arbeta med API-svar

När man hämtar data från ett API är det ofta önskvärt att behandla svarsdatan som oföränderlig, särskilt om du använder den för att rendera UI-komponenter. Readonly-typer kan hjälpa till att förhindra oavsiktliga mutationer av API-datan.


interface ApiResponse {
  readonly userId: number;
  readonly id: number;
  readonly title: string;
  readonly completed: boolean;
}

async function fetchTodo(id: number): Promise<Readonly<ApiResponse>> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  const data: ApiResponse = await response.json();
  return data;
}

fetchTodo(1).then(todo => {
  console.log(todo.title);
  // todo.completed = true; // Fel: Kan inte tilldela till 'completed' eftersom det är en skrivskyddad egenskap.
});

4. Modellera geografisk data (internationellt exempel)

Tänk dig att representera geografiska koordinater. När en koordinat har satts bör den helst förbli konstant. Detta säkerställer dataintegritet, särskilt när man hanterar känsliga applikationer som kartläggnings- eller navigationssystem som verkar över olika geografiska regioner (t.ex. GPS-koordinater för en leveranstjänst som spänner över Nordamerika, Europa och Asien).


interface GeoCoordinates {
 readonly latitude: number;
 readonly longitude: number;
}

const tokyoCoordinates: GeoCoordinates = {
 latitude: 35.6895,
 longitude: 139.6917
};

const newYorkCoordinates: GeoCoordinates = {
 latitude: 40.7128,
 longitude: -74.0060
};


function calculateDistance(coord1: Readonly<GeoCoordinates>, coord2: Readonly<GeoCoordinates>): number {
 // Föreställ dig en komplex beräkning med latitud och longitud
 // Returnerar ett platshållarvärde för enkelhetens skull
 return 1000; 
}

const distance = calculateDistance(tokyoCoordinates, newYorkCoordinates);
console.log("Avstånd mellan Tokyo och New York (platshållare):", distance);

// tokyoCoordinates.latitude = 36.0; // Fel: Kan inte tilldela till 'latitude' eftersom det är en skrivskyddad egenskap.

Djupt skrivskyddade typer: Hantera nästlade objekt

Hjälptypen Readonly<T> gör endast de direkta egenskaperna hos ett objekt readonly. Om ett objekt innehåller nästlade objekt eller arrayer förblir dessa nästlade strukturer föränderliga. För att uppnå äkta djup oföränderlighet måste du rekursivt tillämpa Readonly<T> på alla nästlade egenskaper.

Här är ett exempel på hur man skapar en djupt skrivskyddad typ:


type DeepReadonly<T> = T extends (infer R)[]
  ? DeepReadonlyArray<R>
  : T extends object
  ? DeepReadonlyObject<T>
  : T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
};

interface Company {
  name: string;
  address: {
    street: string;
    city: string;
    country: string;
  };
  employees: string[];
}

const company: DeepReadonly<Company> = {
  name: "Example Corp",
  address: {
    street: "123 Main St",
    city: "Anytown",
    country: "USA",
  },
  employees: ["Alice", "Bob"],
};

// company.name = "New Corp"; // Fel
// company.address.city = "New City"; // Fel
// company.employees.push("Charlie"); // Fel

Denna DeepReadonly<T>-typ tillämpar rekursivt Readonly<T> på alla nästlade egenskaper, vilket säkerställer att hela objektstrukturen är oföränderlig.

Överväganden och avvägningar

Även om oföränderlighet erbjuder betydande fördelar är det viktigt att vara medveten om de potentiella avvägningarna.

Bibliotek för oföränderliga datastrukturer

Flera bibliotek kan förenkla arbetet med oföränderliga datastrukturer i TypeScript:

Bästa praxis för att använda Readonly-typer

För att effektivt utnyttja readonly-typer i dina TypeScript-projekt, följ dessa bästa praxis:

Slutsats: Omfamna oföränderlighet med TypeScript Readonly-typer

TypeScripts readonly-typer är ett kraftfullt verktyg för att bygga mer förutsägbara, underhållbara och robusta applikationer. Genom att omfamna oföränderlighet kan du minska risken för buggar, förenkla felsökning och förbättra den övergripande kvaliteten på din kod. Även om det finns vissa avvägningar att beakta, överväger fördelarna med oföränderlighet ofta kostnaderna, särskilt i komplexa och långlivade projekt. När du fortsätter din TypeScript-resa, gör readonly-typer till en central del av ditt utvecklingsflöde för att frigöra den fulla potentialen hos oföränderlighet och bygga verkligt tillförlitlig programvara.

TypeScript Readonly-typer: Bemästra oföränderliga datastrukturer för robusta applikationer | MLOG