Skip to content

End-to-End Type Safety

ความทรงพลังที่แท้จริงของ TypeScript คือเมื่อ types เชื่อมต่อกันตลอดทั้ง data pipeline — ตั้งแต่ API จนถึง UI

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 และ types
const 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 จาก schema
type Product = z.infer<typeof ProductSchema>;
type ProductList = z.infer<typeof ProductListSchema>;
// Generic fetch wrapper ที่ validate ด้วย Zod
async 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[]
}
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;
}
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>
);
}
}