End-to-End Type Safety
End-to-End Type Safety
Section titled “End-to-End Type Safety”ความทรงพลังที่แท้จริงของ TypeScript คือเมื่อ types เชื่อมต่อกันตลอดทั้ง data pipeline — ตั้งแต่ API จนถึง UI
ภาพรวม Data Flow
Section titled “ภาพรวม Data Flow”API Response (unknown) ↓Zod Validation (runtime check) ↓Typed Data (compile-time safe) ↓React State (useState<T>) ↓Typed UI Components (Props)Step 1: Schema Definition (Single Source of Truth)
Section titled “Step 1: Schema Definition (Single Source of Truth)”import { z } from "zod";
// Schema = source of truth สำหรับทั้ง validation และ typesconst ProductSchema = z.object({ id: z.number(), title: z.string(), price: z.number().positive(), category: z.enum(["electronics", "clothing", "books"]), inStock: z.boolean(), rating: z.number().min(0).max(5).optional(),});
const ProductListSchema = z.object({ products: z.array(ProductSchema), total: z.number(), page: z.number(),});
// Types ที่ infer จาก schematype Product = z.infer<typeof ProductSchema>;type ProductList = z.infer<typeof ProductListSchema>;// JS ไม่มี type safety — ต้องเชื่อว่า API ส่งข้อมูลถูกต้องconst response = await fetch("/api/products");const data = await response.json(); // any — ไม่มี type checkfrom pydantic import BaseModelclass Product(BaseModel): id: int title: str price: float category: strSELECT id, title, price, category, in_stock, ratingFROM productsWHERE in_stock = trueORDER BY rating DESC;Step 2: API Layer with Validation
Section titled “Step 2: API Layer with Validation”// Generic fetch wrapper ที่ validate ด้วย Zodasync function fetchAndValidate<T>( url: string, schema: z.ZodType<T>): Promise<{ ok: true; data: T } | { ok: false; error: string }> { try { const res = await fetch(url); if (!res.ok) { return { ok: false, error: `HTTP ${res.status}` }; } const json = await res.json(); const result = schema.safeParse(json);
if (!result.success) { console.error("Validation failed:", result.error.issues); return { ok: false, error: "Invalid response format" }; } return { ok: true, data: result.data }; } catch { return { ok: false, error: "Network error" }; }}
// ใช้งาน — type-safe!const response = await fetchAndValidate("/api/products", ProductListSchema);if (response.ok) { response.data.products; // type: Product[]}Step 3: Typed State Management
Section titled “Step 3: Typed State Management”import { useState, useEffect } from "react";
type LoadingState = | { status: "idle" } | { status: "loading" } | { status: "success"; data: Product[] } | { status: "error"; message: string };
function useProducts() { const [state, setState] = useState<LoadingState>({ status: "idle" });
useEffect(() => { setState({ status: "loading" });
fetchAndValidate("/api/products", ProductListSchema) .then(result => { if (result.ok) { setState({ status: "success", data: result.data.products }); } else { setState({ status: "error", message: result.error }); } }); }, []);
return state;}Step 4: Typed UI Components
Section titled “Step 4: Typed UI Components”interface ProductCardProps { product: Product; // type จาก Zod schema onAddToCart: (id: number) => void;}
function ProductCard({ product, onAddToCart }: ProductCardProps) { return ( <div className="product-card"> <h3>{product.title}</h3> <p>{product.price.toLocaleString("th-TH")} บาท</p> <span className={`badge badge-${product.category}`}> {product.category} </span> {product.rating && <span>Rating: {product.rating}/5</span>} <button onClick={() => onAddToCart(product.id)} disabled={!product.inStock} > {product.inStock ? "เพิ่มลงตะกร้า" : "สินค้าหมด"} </button> </div> );}
// Page component ที่ใช้ทุกอย่างร่วมกันfunction ProductsPage() { const state = useProducts();
switch (state.status) { case "idle": case "loading": return <p>กำลังโหลด...</p>; case "error": return <p>เกิดข้อผิดพลาด: {state.message}</p>; case "success": return ( <div> {state.data.map(product => ( <ProductCard key={product.id} product={product} onAddToCart={(id) => console.log("Add", id)} /> ))} </div> ); }}