Heim >Web-Frontend >js-Tutorial >Eindeutige Symbole: So verwenden Sie Symbole für die Typensicherheit

Eindeutige Symbole: So verwenden Sie Symbole für die Typensicherheit

Mary-Kate Olsen
Mary-Kate OlsenOriginal
2025-01-16 10:48:09476Durchsuche

Unique Symbols: How to Use Symbols for Type Safety

Wenn Sie einige Zeit mit React gearbeitet haben, sind Sie möglicherweise auf die Funktion queryOptions() von React Query gestoßen. Die Umsetzung sieht erschreckend einfach aus:

export function queryOptions(options: unknown) {
  return options
}

Der wahre Zauber liegt jedoch in den überladenen Funktionssignaturen. Was ist also das Besondere daran?

Wenn Sie sich nicht sicher sind, was eine überladene Funktion ist, können Sie sich diesen Beitrag ansehen: Funktionsüberladung: Umgang mit mehreren Funktionssignaturen

Typisierte Datenbankabfragen

Inspiriert durch den Ansatz von React Query habe ich eine Hilfsfunktion zusammengestellt, die für Leute, die außerhalb von React arbeiten, nützlich sein könnte: eine unkomplizierte Möglichkeit, typisierte Abfragen zu erstellen, zum Beispiel SQL-Abfragen.

export declare const queryParamsSymbol: unique symbol;
export declare const queryReturnSymbol: unique symbol;

export type Query<
  TParams extends Record<string, any> = Record<string, any>,
  TReturn extends Record<string, any> | undefined = undefined,
  TStatement extends string = string,
> = {
  statement: TStatement;
  [queryParamsSymbol]: TParams;
  [queryReturnSymbol]: TReturn;
};

export function query<
  TParams extends Record<string, any> = Record<string, any>,
  TReturn extends Record<string, any> | undefined = undefined,
  TStatement extends string = string,
>(statement: TStatement): Query<TParams, TReturn> {
  return { statement: statement } as Query<TParams, TReturn, TStatement>;
}

Ähnlich wie queryOptions() ist die Funktion selbst ziemlich langweilig: Sie nimmt eine SQL-Anweisung entgegen, verpackt sie in ein Objekt vom Typ Query und gibt sie zurück.

Hier ist ein kurzes Beispiel dafür, wie Sie es nennen würden:

const getUserById = query<{ id: number }, { name: string; email: string }>(
  'SELECT name, email FROM users WHERE id = $id',
);

Beachten Sie, wie wir zwei Typen als generische Parameter übergeben. Der erste sind die erforderlichen Abfrageparameter – in diesem Fall die ID. Die zweite stellt den Rückgabetyp der Abfrage dar – Name und E-Mail.

Unter der Haube bettet query() diese beiden Typen in das zurückgegebene Objekt ein und speichert sie in queryParamsSymbol und queryReturnSymbol. Diese Symbole werden als eindeutige Symbole deklariert, was bedeutet, dass sie nur im Typraum existieren und nicht im transpilierten JavaScript erscheinen.

Ich verwende diese Symbole, um die Parameter- und Rückgabetypen vorübergehend zu speichern und sie bei Bedarf abzurufen.

type InferQueryParams<TQuery> = TQuery extends Query<infer Params, any> ? Params : never;

type UserQueryParams = InferQueryParams<typeof getUserById>;
//        ^? { id: number }

type InferQueryReturn<TQuery> = TQuery extends Query<any, infer Return> ? Return : never;

type UserQueryReturn = InferQueryReturn<typeof getUserById>;
//        ^? { name: string; email: string }

InferQueryParams und InferQueryReturn sind lediglich Dienstprogrammtypen, um zu bestätigen, dass unsere Parameter- und Rückgabetypen korrekt abgeleitet werden. Sie brauchen sie vielleicht nicht wirklich, aber sie sind nützlich, um unseren Ansatz zu überprüfen.

Datenbank-Client

Da wir nun wissen, wie man Parameter- und Rückgabetypen in ein Abfrageobjekt einbettet, wie führen wir diese Abfragen tatsächlich aus? Werfen wir einen Blick auf einen einfachen Datenbank-Client, der unsere typisierten Abfragen ausführen kann:

class DatabaseClient {
  async execute<
    TParams extends Record<string, any>, 
    TReturn extends Record<string, any>
  >(
    query: Query<TParams, TReturn>,
    params: TParams,
  ): Promise<Array<TReturn>> {
    // execute the query and return the results
    // ...
    return [];
  }
}

const db = new DatabaseClient();

// Return type and parameters are inferred from the query object
const result = await db.execute(getUserById, { id: 1 });
//                                              ^? { id: number }
//      ^? Array<{ name: string; email: string }>
console.log(result);

In diesem Beispiel übergeben wir unser typisiertes getUserById-Abfrageobjekt an die db.execute()-Methode. Da der Abfragetyp sowohl Parameter- als auch Rückgabetypinformationen enthält, leitet TypeScript diese automatisch ab. Wir können dies leicht bestätigen, indem wir mit der Maus über das Ergebnis fahren. TypeScript schlägt außerdem die ID als Eigenschaft unseres Parameterobjekts vor.

TypeScript-Spielplatz

Das vollständige Codebeispiel finden Sie in diesem TypeScript Playground.

Das obige ist der detaillierte Inhalt vonEindeutige Symbole: So verwenden Sie Symbole für die Typensicherheit. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn