Maison > interface Web > js tutoriel > Symboles uniques : comment utiliser les symboles pour la sécurité des types

Symboles uniques : comment utiliser les symboles pour la sécurité des types

Mary-Kate Olsen
Libérer: 2025-01-16 10:48:09
original
470 Les gens l'ont consulté

Unique Symbols: How to Use Symbols for Type Safety

Si vous avez passé du temps à travailler avec React, vous êtes peut-être tombé sur la fonction queryOptions() de React Query. Sa mise en œuvre semble incroyablement simple :

export function queryOptions(options: unknown) {
  return options
}
Copier après la connexion

Cependant, la vraie magie réside dans ses signatures de fonctions surchargées. Alors, qu’est-ce qu’il a de si spécial ?

Si vous n'êtes pas sûr de ce qu'est une fonction surchargée, vous pouvez consulter cet article : Surcharge de fonctions : comment gérer les signatures de fonctions multiples

Requêtes de base de données typées

Inspiré par l'approche de React Query, j'ai mis au point une fonction d'assistance qui pourrait être utile aux personnes travaillant en dehors de React : un moyen simple de créer des requêtes typées, par exemple des requêtes SQL.

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>;
}
Copier après la connexion

Semblable à queryOptions(), la fonction elle-même est assez ennuyeuse : elle prend une instruction SQL, l'enveloppe dans un objet de type Query et la renvoie.

Voici un exemple rapide de la façon dont vous l'appelleriez :

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

Remarquez comment nous passons deux types comme paramètres génériques. Le premier concerne les paramètres de requête requis – dans ce cas, l’identifiant. Le second représente le type de retour de la requête : nom et e-mail.

Sous le capot, query() intègre ces deux types dans l'objet renvoyé, en les cachant dans queryParamsSymbol et queryReturnSymbol. Ces symboles sont déclarés comme symboles uniques, ce qui signifie qu'ils n'existent que dans l'espace de type et n'apparaissent pas dans le JavaScript transpilé.

J'utilise ces symboles pour stocker temporairement les paramètres et les types de retour et les récupérer chaque fois que j'en ai besoin.

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 }
Copier après la connexion

InferQueryParams et InferQueryReturn ne sont que des types d'utilitaires permettant de confirmer que nos paramètres et nos types de retour sont déduits correctement. Vous n’en avez peut-être pas réellement besoin, mais ils sont pratiques pour vérifier notre approche.

Client de base de données

Maintenant que nous savons comment intégrer des paramètres et des types de retour dans un objet de requête, comment pouvons-nous réellement exécuter ces requêtes ? Jetons un coup d'œil à un client de base de données simple capable d'exécuter nos requêtes tapées :

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);
Copier après la connexion

Dans cet exemple, nous transmettons notre objet de requête getUserById tapé à la méthode db.execute(). Étant donné que le type Query contient à la fois les informations sur le paramètre et le type de retour, TypeScript les déduit automatiquement. Nous pouvons facilement le confirmer en survolant le résultat et TypeScript suggérera également l'identifiant comme propriété de notre objet paramètres.

Terrain de jeu dactylographié

Vous pouvez trouver l'exemple de code complet dans ce TypeScript Playground.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal