Back

TypeScript Dictionary: Vollständiger Leitfaden für typsichere Objekte

TypeScript Dictionary: Vollständiger Leitfaden für typsichere Objekte

Wenn Sie mit Schlüssel-Wert-Paaren in TypeScript arbeiten, benötigen Sie eine zuverlässige Methode, um die Typsicherheit beim Zugriff auf und bei der Manipulation von Daten zu gewährleisten. Im Gegensatz zu Sprachen mit eingebauten Dictionary-Typen erfordert TypeScript spezifische Ansätze, um typsichere Dictionaries zu erstellen. Dieser Leitfaden untersucht alle Implementierungsmöglichkeiten mit praktischen Beispielen, die Ihnen helfen, den richtigen Ansatz für Ihre Projekte zu wählen.

Wichtige Erkenntnisse

  • TypeScript bietet mehrere Möglichkeiten zur Implementierung von Dictionaries: Index-Signaturen, Record-Utility und Map
  • Wählen Sie die Implementierung nach Ihren Bedürfnissen: Index-Signaturen für einfache Dictionaries, Record für spezifische Schlüsselmengen und Map für erweiterte Funktionen
  • Nutzen Sie das Typsystem von TypeScript, um die Sicherheit zur Kompilierzeit für Dictionary-Operationen zu gewährleisten
  • Berücksichtigen Sie Leistungsimplikationen bei der Auswahl zwischen objektbasierten Dictionaries und Map
  • Verwenden Sie fortgeschrittene Typentechniken wie Mapped Types für komplexe Dictionary-Anforderungen

Was ist ein TypeScript Dictionary?

Ein Dictionary in TypeScript ist eine Datenstruktur, die Schlüssel-Wert-Paare speichert und effiziente Nachschlagungen nach Schlüsseln ermöglicht. Obwohl TypeScript keinen nativen Dictionary-Typ hat, bietet es mehrere Möglichkeiten, dictionaryähnliche Strukturen mit vollständiger Typsicherheit zu implementieren.

Ein typsicheres Dictionary gewährleistet:

  • Schlüssel und Werte entsprechen bestimmten Typen
  • Kompilierzeitfehler bei Typfehlern
  • Unterstützung für Autovervollständigung und IntelliSense
  • Schutz vor Laufzeitfehlern

Dictionary-Implementierungsoptionen in TypeScript

Verwendung von Index-Signaturen

Der einfachste Ansatz verwendet JavaScript-Objekte mit TypeScript-Index-Signaturen:

// Basic index signature dictionary
const userScores: { [username: string]: number } = {};

// Adding entries
userScores[""alice""] = 95;
userScores[""bob""] = 87;

// Accessing values
console.log(userScores[""alice""]); // 95

// Type safety in action
userScores[""charlie""] = ""high""; // Error: Type 'string' is not assignable to type 'number'

Dieser Ansatz ist einfach, wird aber umständlich, wenn er in Ihrem Codebase wiederverwendet wird.

Erstellen eines wiederverwendbaren Dictionary-Typs

Für bessere Wiederverwendbarkeit definieren Sie einen generischen Dictionary-Typ:

// Generic dictionary type
type Dictionary<K extends string | number | symbol, V> = {
  [key in K]: V;
};

// Usage with string keys and number values
const scores: Dictionary<string, number> = {};
scores[""math""] = 95;
scores[""science""] = 87;

// Usage with numeric keys
const idMapping: Dictionary<number, string> = {};
idMapping[1] = ""user_a"";
idMapping[2] = ""user_b"";

Verwendung des Record-Utility-Typs

Der eingebaute Record-Utility-Typ von TypeScript bietet eine übersichtlichere Syntax:

// Using Record for fixed set of keys
type UserFields = ""name"" | ""email"" | ""role"";
const user: Record<UserFields, string> = {
  name: ""Alice Smith"",
  email: ""alice@example.com"",
  role: ""Admin""
};

// Using Record with any string keys
const config: Record<string, any> = {};
config[""apiUrl""] = ""https://api.example.com"";
config[""timeout""] = 5000;

Der Record-Typ ist besonders nützlich, wenn Sie einen bestimmten Satz von Schlüsseln mit String-Literal-Typen erzwingen müssen.

Verwendung von JavaScript’s Map mit TypeScript

Für erweiterte Funktionen verwenden Sie das Map-Objekt von JavaScript mit TypeScript-Generics:

// Type-safe Map
const userProfiles = new Map<string, {age: number, active: boolean}>();

// Adding entries
userProfiles.set(""alice"", {age: 28, active: true});
userProfiles.set(""bob"", {age: 34, active: false});

// Type checking works
userProfiles.set(""charlie"", {age: ""thirty""}); // Error: Type 'string' not assignable to type 'number'

// Accessing values
const aliceProfile = userProfiles.get(""alice"");
console.log(aliceProfile?.age); // 28

Vergleich der Dictionary-Implementierungen

Funktion Objekt mit Index-Signatur Record-Typ Map Schlüsseltypen String, number, symbol Beliebiger Typ Beliebiger Typ Leistung Schnell für kleine Datensätze Schnell für kleine Datensätze Besser für häufige Hinzufügungen/Löschungen Speicherverbrauch Niedriger Niedriger Höher Reihenfolgeerhaltung Nein Nein Ja (Einfügereihenfolge) Iteration Erfordert Object.entries() Erfordert Object.entries() Eingebaute Iteratoren Spezielle Methoden Nein Nein has(), delete(), clear() Risiko der Prototype-Verschmutzung Ja Ja Nein

Häufige Dictionary-Operationen

Prüfen, ob ein Schlüssel existiert

// Using index signature
const hasKey = (dict: Record<string, unknown>, key: string): boolean => {
  return key in dict;
};

// Using Map
const userMap = new Map<string, number>();
userMap.has(""alice""); // true/false

Hinzufügen und Aktualisieren von Werten

// Using index signature
type UserDict = Record<string, {name: string, role: string}>;
const users: UserDict = {};

// Adding
users[""u1""] = {name: ""Alice"", role: ""Admin""};

// Updating
users[""u1""] = {...users[""u1""], role: ""User""};

// Using Map
const userMap = new Map<string, {name: string, role: string}>();
userMap.set(""u1"", {name: ""Alice"", role: ""Admin""});
userMap.set(""u1"", {name: ""Alice"", role: ""User""}); // Updates existing entry

Löschen von Schlüsseln

// Using index signature
delete users[""u1""];

// Using Map
userMap.delete(""u1"");

Iteration durch Dictionaries

// Object-based dictionary iteration
const scores: Record<string, number> = {math: 95, science: 87};

// Method 1: Object.entries()
for (const [subject, score] of Object.entries(scores)) {
  console.log(`${subject}: ${score}`);
}

// Method 2: Object.keys()
Object.keys(scores).forEach(subject => {
  console.log(`${subject}: ${scores[subject]}`);
});

// Map iteration
const scoreMap = new Map<string, number>([
  [""math"", 95],
  [""science"", 87]
]);

// Direct iteration
for (const [subject, score] of scoreMap) {
  console.log(`${subject}: ${score}`);
}

// Using forEach
scoreMap.forEach((score, subject) => {
  console.log(`${subject}: ${score}`);
});

Fortgeschrittene Typsicherheitstechniken

Einschränkung von Schlüsseltypen

// Using string literal union types
type ValidKeys = ""id"" | ""name"" | ""email"";
const userData: Record<ValidKeys, string> = {
  id: ""123"",
  name: ""Alice"",
  email: ""alice@example.com""
};

// Attempting to use invalid keys
userData.phone = ""555-1234""; // Error: Property 'phone' does not exist

Schreibgeschützte Dictionaries

// Immutable dictionary
const constants: Readonly<Record<string, number>> = {
  MAX_USERS: 100,
  TIMEOUT_MS: 5000
};

constants.MAX_USERS = 200; // Error: Cannot assign to 'MAX_USERS' because it is a read-only property

Teilweise Dictionaries

// Dictionary with optional values
type UserProfile = {
  name: string;
  email: string;
  age: number;
};

// Create a dictionary where each value can have some or all UserProfile properties
const profiles: Record<string, Partial<UserProfile>> = {};

profiles[""alice""] = { name: ""Alice"" }; // Valid even without email and age
profiles[""bob""] = { name: ""Bob"", email: ""bob@example.com"" }; // Valid

Verwendung von Mapped Types für fortgeschrittene Dictionaries

type SensorData = {
  temperature: number;
  humidity: number;
  pressure: string;
  metadata: object;
};

// Create a dictionary type that only includes number properties from SensorData
type NumericSensorDict = {
  [K in keyof SensorData as SensorData[K] extends number ? K : never]: boolean;
};

// The resulting type only has temperature and humidity as valid keys
const sensorStatus: NumericSensorDict = {
  temperature: true,
  humidity: false
};

Leistungsüberlegungen

Bei der Auswahl einer Dictionary-Implementierung sollten Sie berücksichtigen:

  1. Datengröße: Für kleine Datensätze mit String-Schlüsseln sind objektbasierte Dictionaries effizient
  2. Häufige Änderungen: Map bietet bessere Leistung bei häufigen Hinzufügungen und Löschungen
  3. Speicherverbrauch: Objektbasierte Dictionaries verbrauchen weniger Speicher als Map
  4. Schlüsseltypen: Wenn Sie Nicht-String-Schlüssel benötigen, verwenden Sie Map

Benchmark-Ergebnisse für 10.000 Operationen:

Object access: ~25ms
Map access: ~45ms
Object insertion: ~30ms
Map insertion: ~50ms
Object deletion: ~20ms
Map deletion: ~15ms

Anwendungsfälle aus der Praxis

Konfigurationsmanagement

type AppConfig = Record<string, string | number | boolean>;

const config: AppConfig = {
  apiUrl: ""https://api.example.com"",
  timeout: 5000,
  enableCache: true
};

Cache-Implementierung

class Cache<T> {
  private store = new Map<string, {value: T, expiry: number}>();
  
  set(key: string, value: T, ttlSeconds: number): void {
    const expiry = Date.now() + (ttlSeconds * 1000);
    this.store.set(key, {value, expiry});
  }
  
  get(key: string): T | null {
    const item = this.store.get(key);
    
    if (!item) return null;
    if (item.expiry < Date.now()) {
      this.store.delete(key);
      return null;
    }
    
    return item.value;
  }
}

const userCache = new Cache<{name: string, role: string}>();
userCache.set(""user:123"", {name: ""Alice"", role: ""Admin""}, 300);

Zustandsmanagement in React

interface UserState {
  users: Record<string, {name: string, role: string}>;
  loading: boolean;
  error: string | null;
}

const initialState: UserState = {
  users: {},
  loading: false,
  error: null
};

// In a reducer
function userReducer(state = initialState, action: any): UserState {
  switch (action.type) {
    case 'ADD_USER':
      return {
        ...state,
        users: {
          ...state.users,
          [action.payload.id]: {
            name: action.payload.name,
            role: action.payload.role
          }
        }
      };
    // Other cases...
    default:
      return state;
  }
}

Fehlerbehandlungsstrategien

Umgang mit fehlenden Schlüsseln

function safeGet<K extends string, V>(
  dict: Record<K, V>, 
  key: string, 
  defaultValue: V
): V {
  return (key in dict) ? dict[key as K] : defaultValue;
}

const users: Record<string, string> = {
  ""1"": ""Alice""
};

const userName = safeGet(users, ""2"", ""Unknown User"");
console.log(userName); // ""Unknown User""

Type Guards für Dictionary-Werte

function isUserDict(obj: unknown): obj is Record<string, {name: string, age: number}> {
  if (typeof obj !== 'object' || obj === null) return false;
  
  for (const key in obj as object) {
    const value = (obj as any)[key];
    if (typeof value !== 'object' || 
        typeof value.name !== 'string' || 
        typeof value.age !== 'number') {
      return false;
    }
  }
  
  return true;
}

// Usage
function processUserData(data: unknown) {
  if (isUserDict(data)) {
    // TypeScript knows data is Record<string, {name: string, age: number}>
    for (const [id, user] of Object.entries(data)) {
      console.log(`User ${id}: ${user.name}, ${user.age}`);
    }
  }
}

Fazit

TypeScript-Dictionaries bieten leistungsstarke Möglichkeiten, Schlüssel-Wert-Daten mit vollständiger Typsicherheit zu verwalten. Indem Sie den richtigen Implementierungsansatz basierend auf Ihren spezifischen Anforderungen wählen, können Sie robustere Anwendungen mit weniger Laufzeitfehlern erstellen. Ob Sie einfache string-basierte Objekte oder komplexe Dictionaries mit erweiterten Funktionen benötigen, das Typsystem von TypeScript stellt sicher, dass Ihre Dictionary-Operationen im gesamten Codebase typsicher bleiben.

FAQs

Verwenden Sie Map, wenn Sie Nicht-String-Schlüssel, garantierte Schlüsselreihenfolge, häufige Hinzufügungen und Löschungen oder Schutz vor Prototype-Verschmutzung benötigen. Maps bieten eingebaute Methoden wie has(), delete() und clear(), die bestimmte Operationen bequemer machen

Verwenden Sie String-Literal-Typen mit dem Record-Utility-Typ:nn`typescriptntype AllowedKeys = 'id' | 'name' | 'email';nconst user: Record<AllowedKeys, string> = {n id: '123',n name: 'Alice',n email: 'alice@example.com'n};n`nnDies stellt sicher, dass TypeScript einen Fehler ausgibt, wenn Sie versuchen, Schlüssel zu verwenden, die nicht in der AllowedKeys-Union enthalten sind

Nicht mit objektbasierten Dictionaries, aber mit Map:nn`typescriptnconst userMap = new Map<{id: number}, string>();nconst key1 = {id: 1};nconst key2 = {id: 1}; // Different object referencennuserMap.set(key1, 'Alice');nconsole.log(userMap.get(key1)); // 'Alice'nconsole.log(userMap.get(key2)); // undefined (different object reference)n`nnBeachten Sie, dass Map Referenzgleichheit für Objektschlüssel verwendet

Objekt in Map konvertieren:nn`typescriptnconst obj: Record<string, number> = {a: 1, b: 2};nconst map = new Map(Object.entries(obj));n`nnMap in Objekt konvertieren:nn`typescriptnconst mapInstance = new Map<string, number>([['a', 1], ['b', 2]]);nconst objFromMap = Object.fromEntries(mapInstance);n`

Verwenden Sie optionales Verketten und Nullish Coalescing:nn`typescriptnconst users: Record<string, {name: string} | undefined> = {n '1': {name: 'Alice'}n};nn// Safe access with fallbacknconst userName = users['2']?.name ?? 'Unknown';nconsole.log(userName); // 'Unknown'n`nnDies verhindert Laufzeitfehler beim Zugriff auf Eigenschaften von potenziell undefinierten Werten

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers