如果您是前端工程師,您可能遇到過這樣的情況:您被要求在服務後端部分的 API 之前開始實現某個功能功能存在。工程師通常會轉向模擬來實現並行開發(這意味著該功能的前端和後端部分是並行開發的)。
然而,Mocking 可能會帶來一些缺點。第一個也是最明顯的一點是模擬可能會偏離實際實現,導致它們不可靠。第二個問題是模擬通常很冗長;對於包含大量資料的模擬,可能不清楚某個模擬反應實際上在模擬什麼。
以下資料是您可能在程式碼庫中找到的一些資料的範例:
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... ];
我每天使用的數據看起來都很像這樣。訂單數組或某種以客戶為中心的信息,具有嵌套值,可幫助填充詳細說明各種信息的表格、彈出窗口和卡片。
作為一名負責維護嚴重依賴此類模擬的應用程式的工程師,您可能會問「回應模擬中的這個特定物件是什麼?」。我經常發現自己滾動瀏覽數百個範例,就像上面的範例一樣,但不確定每個物件的用途是什麼。
隨著我對自己作為工程師的身份越來越有信心,我給自己下定決心要解決上述問題;如果每個模擬都可以更輕鬆地展示其目的怎麼辦?如果工程師只需要寫他們打算模擬的行怎麼辦?
在使用一些程式碼和一個名為 Zod 的函式庫時,我發現了以下稱為 parse 的方法,它嘗試根據已知類型驗證傳入資料:
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
這是一個靈光乍現的時刻; Zod 文件中的這個小例子正是我一直在尋找的!如果解析方法可以接受一個值並回傳它,那麼如果我傳入一個值,我就會得到它。我也已經知道我可以為 Zod 模式定義預設值。如果傳遞空物件將傳回一個完整的物件及其值怎麼辦?你瞧,確實如此;我可以在 Zod 模式上定義預設值,並傳回預設值:
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
現在我有了一種生成物件的方法,但它仍然不是我想要的。我真正想要的是一種僅寫下我正在「嘲笑」的確切行的方法。一個簡單的解決方案可能如下所示:
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
然而這也有其自身的缺陷;如果我希望覆蓋的值本身就是一個物件或陣列怎麼辦?然後,我必須手動輸入該功能之前所需的每一行才能繼續工作並按預期被嘲笑,這違背了我們正在進行的解決方案的目的。
很長一段時間以來,這就是我所得到的,直到最近我再次嘗試改進上述內容。第一步是定義「API」;我希望我的使用者如何與此功能互動?
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... ];
上面的 API 將允許使用者指定他們選擇的模式,然後提供適當的覆寫並傳回一個使用者物件!當然,我們希望正確考慮數組以及單一物件。為此,對傳入的覆蓋類型進行簡單的類型檢查就足夠了:
const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" stringSchema.parse(12); // throws error
上面的程式碼實際上與之前相同,但現在它在內部封裝了解析,因此使用者不必手動執行此操作或了解有關 Zods 解析方法的詳細資訊。正如您透過閱讀包含的if/else 語句可能猜到的那樣,我們還透過使用遞歸建構器函數來解決巢狀物件和陣列的保存問題,該函數解析每個值並傳回Zod 模式中指定的預設值。 🎜>
上面的內容有點讓人費解,但結果是使用者可以執行以下操作:
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
這已經是一篇相當長的文章了,所以讓我們以我們所有努力工作的結果來結束吧。讓我們回顧一下第一個模擬,以及如何使用 zodObjectBuilder 來寫它。首先讓我們定義我們的類型和預設值,並將結果模式傳遞到 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
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
透過這個小演示,我們已經完成了第一篇文章的結尾:) 我希望您喜歡閱讀這段探索改進模擬的旅程。 zodObjectBuilder 仍在建置中,但它很好地滿足了我最小化模擬物件的需求。如果您想嘗試目前版本,可以在 https://www.npmjs.com/package/@crbroughton/ts-utils 找到它,其中包含該功能。
以上是我如何嘗試用 Zod 改進模擬的詳細內容。更多資訊請關注PHP中文網其他相關文章!