Maison >interface Web >js tutoriel >Symboles uniques : comment utiliser les symboles pour la sécurité des types
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 }
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
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>; }
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', );
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 }
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.
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);
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.
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!