Si vous êtes un ingénieur front-end, vous avez probablement été dans une situation où vous avez dû commencer à implémenter une fonctionnalité avant l'API qui sert la partie back-end de celle-ci. fonctionnalité existe. Les ingénieurs se tournent souvent vers la simulation pour permettre le développement parallèle (ce qui signifie que les parties front-end et back-end de la fonctionnalité sont développées en parallèle).
Les moqueries peuvent cependant présenter certains inconvénients. La première et la plus évidente est que les simulations peuvent s’écarter de la mise en œuvre réelle, ce qui les rend peu fiables. Un deuxième problème est que les moqueries peuvent souvent être verbeuses ; Avec les simulations qui contiennent beaucoup de données, il peut être difficile de savoir de quoi se moque réellement une certaine réponse simulée.
Les données ci-dessous sont un exemple de certaines données que vous pourriez trouver dans une base de code :
type Order = { orderId: string; customerInfo: CustomerInfo; // omitted these types for brevity orderDate: string; items: OrderItem[]; paymentInfo: PaymentInfo; subtotal: number; shippingCost: number; tax: number; totalAmount: number; status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; trackingNumber: string | null; }; const mockOrders: Order[] = [ { orderId: "ORD-2024-001", customerInfo: { id: "CUST-1234", name: "Alice Johnson", email: "alice.j@email.com", shippingAddress: { street: "123 Pine Street", city: "Portland", state: "OR", zipCode: "97201", country: "USA" } }, orderDate: "2024-03-15T14:30:00Z", items: [ { productId: "PROD-789", name: "Organic Cotton T-Shirt", quantity: 2, pricePerUnit: 29.99, color: "Navy", size: "M" }, { productId: "PROD-456", name: "Recycled Canvas Tote", quantity: 1, pricePerUnit: 35.00, color: "Natural" } ], paymentInfo: { method: "credit_card", status: "completed", transactionId: "TXN-88776655" }, subtotal: 94.98, shippingCost: 5.99, tax: 9.50, totalAmount: 110.47, status: "shipped", trackingNumber: "1Z999AA1234567890" }, // Imagine more objects here, with various values changed... ];
Les données avec lesquelles je travaille quotidiennement ressemblent beaucoup à ceci. Des tableaux de commandes ou une sorte d'informations axées sur le client, comportant des valeurs imbriquées qui aident à remplir des tableaux, des fenêtres contextuelles et des cartes détaillées avec toutes sortes d'informations.
En tant qu'ingénieur chargé de maintenir des applications qui s'appuient fortement sur de telles simulations, vous pourriez vous demander « quel est cet objet particulier dans la réponse moqueuse ? ». Je me suis souvent retrouvé à parcourir des centaines d'exemples comme celui ci-dessus, sans être sûr du but de chaque objet.
Au fur et à mesure que je suis devenu plus sûr de moi en tant qu'ingénieur, je me suis chargé de résoudre le problème ci-dessus ; Et si chaque maquette pouvait afficher plus facilement son objectif ? Et si un ingénieur n’avait qu’à écrire les lignes dont il a l’intention de se moquer ?
En jouant avec du code et une bibliothèque appelée Zod, j'ai trouvé la méthode suivante appelée parse, qui tente de valider les données entrantes par rapport à un type connu :
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
C'était un moment d'éclairage ; Ce petit exemple dans la documentation de Zod était exactement ce que je cherchais ! Si la méthode d'analyse pouvait accepter une valeur et la renvoyer, alors si je transmettais une valeur, je la récupérerais. Je savais aussi déjà que je pouvais définir des valeurs par défaut pour un schéma Zod. Et si passer un objet vide renvoyait un objet plein, avec ses valeurs ? Et voilà, c’est le cas ; Je pourrais définir des valeurs par défaut sur un schéma Zod et renvoyer les valeurs par défaut :
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) // returns a full user object
Maintenant, j'avais un moyen de générer des objets, mais ce n'était toujours pas tout à fait ce que je cherchais. Ce que je voulais vraiment, c'était un moyen de seulement écrire les lignes exactes dont je me « moquais ». Une solution simple pourrait ressembler à :
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) const overridenUser = {...user, ...{ name: "My new name", settings: {}, // I would need to write every key:value for settings :( } satisfies Partial<z.infer<typeof UserSchema>>} // overrides the base object
Cependant, cela a ses propres défauts ; Que se passe-t-il si la valeur que je souhaite remplacer est elle-même un objet ou un tableau ? Je devrais alors taper manuellement chaque ligne qui était auparavant requise pour que cette fonctionnalité continue de fonctionner et soit moquée comme prévu, ce qui va à l'encontre de l'objectif de notre solution de travail en cours.
Pendant longtemps, c'est tout ce que j'en étais, jusqu'à très récemment, lorsque j'ai eu une autre tentative d'améliorer ce qui précède. La première étape consistait à définir « l'API » ; Comment voulais-je que mes utilisateurs interagissent avec cette fonctionnalité ?
type Order = { orderId: string; customerInfo: CustomerInfo; // omitted these types for brevity orderDate: string; items: OrderItem[]; paymentInfo: PaymentInfo; subtotal: number; shippingCost: number; tax: number; totalAmount: number; status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'; trackingNumber: string | null; }; const mockOrders: Order[] = [ { orderId: "ORD-2024-001", customerInfo: { id: "CUST-1234", name: "Alice Johnson", email: "alice.j@email.com", shippingAddress: { street: "123 Pine Street", city: "Portland", state: "OR", zipCode: "97201", country: "USA" } }, orderDate: "2024-03-15T14:30:00Z", items: [ { productId: "PROD-789", name: "Organic Cotton T-Shirt", quantity: 2, pricePerUnit: 29.99, color: "Navy", size: "M" }, { productId: "PROD-456", name: "Recycled Canvas Tote", quantity: 1, pricePerUnit: 35.00, color: "Natural" } ], paymentInfo: { method: "credit_card", status: "completed", transactionId: "TXN-88776655" }, subtotal: 94.98, shippingCost: 5.99, tax: 9.50, totalAmount: 110.47, status: "shipped", trackingNumber: "1Z999AA1234567890" }, // Imagine more objects here, with various values changed... ];
L'API ci-dessus permettrait à l'utilisateur de spécifier un schéma de son choix, puis de fournir les remplacements appropriés et de renvoyer un objet utilisateur ! Bien sûr, nous voudrions tenir compte correctement des tableaux ainsi que d'un seul objet. À cette fin, une simple vérification de type par rapport au type de remplacement entrant s'est avérée suffisante :
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
Ce qui précède est effectivement le même code qu'avant, mais il encapsule désormais l'analyse en interne afin que les utilisateurs n'aient pas à le faire manuellement ou à connaître des informations détaillées sur la méthode d'analyse de Zods. Comme vous l'avez peut-être deviné en lisant l'instruction if/else incluse, nous avons également résolu la préservation des objets et des tableaux imbriqués grâce à l'utilisation d'une fonction de création récursive qui analyse chaque valeur et renvoie ses valeurs par défaut spécifiées dans le schéma Zod.
Ce qui précède est assez compliqué à comprendre, mais le résultat est qu'un utilisateur peut faire ce qui suit :
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) // returns a full user object
En fournissant l'option de configuration verifyNestedDefaults au constructeur, un utilisateur peut conserver les paires clé-valeur dans un objet ou un tableau imbriqué ! Cela résout le problème d'un utilisateur remplaçant une clé qui n'est pas un type primitif comme une chaîne, mais plutôt un type plus complexe et conserve toutes les valeurs moins celles que nous choisissons de remplacer.
Cela a déjà été une sacrée lecture, alors terminons par le résultat de tout notre travail acharné. Revenons sur cette première simulation et comment nous pourrions l'écrire avec zodObjectBuilder. Définissons d’abord nos types et nos valeurs par défaut, et passons le schéma résultant dans le zodObjectBuilder :
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = UserSchema.parse({}) const overridenUser = {...user, ...{ name: "My new name", settings: {}, // I would need to write every key:value for settings :( } satisfies Partial<z.infer<typeof UserSchema>>} // overrides the base object
L'implémentation ci-dessus renverra un seul objet en utilisant toutes les valeurs par défaut ! Mais nous pouvons faire mieux que cela, nous pouvons maintenant (avec l'aide de quelques définitions de surcharge et d'une analyse interne) créer des tableaux d'objets, parfaits pour le cas d'utilisation des réponses API moqueuses :
const UserSchema = z.object({ id: z.string().default('1'), name: z.string().default('Craig R Broughton'), settings: z.object({ theme: z.enum(['light', 'dark']), notifications: z.boolean() }).default({ theme: 'dark', notifications: true, }) }); const user = zodObjectBuilder({ schema: UserSchema, overrides: { name: 'My new name', settings: { theme: 'dark' } } // setting is missing the notifications theme :( }); // returns a full user object with the overrides
Ce qui précède génère un tableau de commandes avec les valeurs par défaut complètes, avec des statuts de livraison remplacés ! Espérons que cela démontre comment la fonction zodObjectBuilder peut minimiser l'effort requis pour créer une nouvelle simulation basée sur un schéma de type sécurisé fiable.
Avec cette petite démonstration, nous avons atteint la fin de mon premier article :) J'espère que vous avez apprécié la lecture de ce voyage d'exploration pour améliorer la moquerie. zodObjectBuilder est toujours en cours de construction, mais il répond bien à mes besoins pour minimiser les objets simulés. Si vous souhaitez jouer avec la version actuelle, vous pouvez la trouver sur https://www.npmjs.com/package/@crbroughton/ts-utils qui inclut la fonction.
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!