การสร้าง Components สมัยใหม่ด้วย Astro และ TypeScript
เรียนรู้วิธีการสร้าง UI components ที่มีประสิทธิภาพและนำกลับมาใช้ได้ด้วย Astro และ TypeScript
การสร้าง Components สมัยใหม่ด้วย Astro และ TypeScript
การสร้าง components ที่ดีเป็นรากฐานสำคัญของการพัฒนาเว็บไซต์สมัยใหม่ ในบทความนี้เราจะมาเรียนรู้วิธีการสร้าง components ด้วย Astro และ TypeScript กัน
ทำไมต้องใช้ TypeScript?
TypeScript ช่วยให้เราสามารถ:
- Type Safety: ป้องกันข้อผิดพลาดได้ตั้งแต่เวลาเขียนโค้ด
- Better IntelliSense: ได้รับการช่วยเหลือจาก IDE มากขึ้น
- Self-documenting: โค้ดอธิบายตัวเองได้ดีขึ้น
- Refactoring: เปลี่ยนแปลงโค้ดได้อย่างมั่นใจ
การสร้าง Button Component
เริ่มต้นด้วย Button component ที่มี variants หลายแบบ:
---
// Button.astro
export interface Props {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
href?: string;
type?: 'button' | 'submit' | 'reset';
class?: string;
}
const {
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
href,
type = 'button',
class: className = '',
...rest
} = Astro.props;
const baseClasses = 'inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none';
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500',
outline: 'border border-gray-300 bg-transparent hover:bg-gray-50 text-gray-700 focus:ring-gray-500',
ghost: 'bg-transparent hover:bg-gray-100 text-gray-700 focus:ring-gray-500'
};
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm rounded',
md: 'px-4 py-2 text-base rounded-md',
lg: 'px-6 py-3 text-lg rounded-lg'
};
const classes = [
baseClasses,
variantClasses[variant],
sizeClasses[size],
className
].join(' ');
const Element = href ? 'a' : 'button';
---
<Element
class={classes}
disabled={disabled || loading}
type={href ? undefined : type}
href={href}
{...rest}
>
{loading && (
<svg class="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
)}
<slot />
</Element>
การสร้าง Card Component
Card component ที่ใช้งานได้หลากหลาย:
---
// Card.astro
export interface Props {
variant?: 'default' | 'elevated' | 'outlined';
padding?: 'none' | 'sm' | 'md' | 'lg';
class?: string;
}
const {
variant = 'default',
padding = 'md',
class: className = ''
} = Astro.props;
const baseClasses = 'bg-white rounded-lg transition-all duration-200';
const variantClasses = {
default: 'shadow-sm border border-gray-200',
elevated: 'shadow-lg hover:shadow-xl',
outlined: 'border-2 border-gray-200 hover:border-gray-300'
};
const paddingClasses = {
none: '',
sm: 'p-4',
md: 'p-6',
lg: 'p-8'
};
const classes = [
baseClasses,
variantClasses[variant],
paddingClasses[padding],
className
].join(' ');
---
<div class={classes}>
<slot />
</div>
การใช้งาน Components
ใช้งาน components ที่เราสร้างขึ้น:
---
// pages/components-demo.astro
import Button from '../components/Button.astro';
import Card from '../components/Card.astro';
---
<html>
<head>
<title>Components Demo</title>
</head>
<body>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">Components Demo</h1>
<!-- Button Examples -->
<Card class="mb-8">
<h2 class="text-xl font-semibold mb-4">Buttons</h2>
<div class="space-x-4">
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button loading={true}>Loading</Button>
</div>
</Card>
<!-- Card Examples -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card variant="default">
<h3 class="font-semibold mb-2">Default Card</h3>
<p class="text-gray-600">This is a default card with shadow.</p>
</Card>
<Card variant="elevated">
<h3 class="font-semibold mb-2">Elevated Card</h3>
<p class="text-gray-600">This card has elevated shadow.</p>
</Card>
<Card variant="outlined">
<h3 class="font-semibold mb-2">Outlined Card</h3>
<p class="text-gray-600">This card has a border outline.</p>
</Card>
</div>
</div>
</body>
</html>
Best Practices
1. Interface Definition
export interface Props {
// Required props ไม่ต้องมี default value
title: string;
// Optional props ควรมี default value
variant?: 'primary' | 'secondary';
disabled?: boolean;
// HTML attributes
class?: string;
id?: string;
}
2. Props Destructuring
---
const {
title,
variant = 'primary',
disabled = false,
class: className = '',
...rest
} = Astro.props;
---
3. Conditional Classes
const classes = [
'base-class',
variant === 'primary' ? 'primary-class' : 'secondary-class',
disabled && 'disabled-class',
className
].filter(Boolean).join(' ');
การทดสอบ Components
สร้างหน้าทดสอบสำหรับ components:
---
// pages/component-tests.astro
import Button from '../components/Button.astro';
const buttonVariants = ['primary', 'secondary', 'outline', 'ghost'] as const;
const buttonSizes = ['sm', 'md', 'lg'] as const;
---
<html>
<head>
<title>Component Tests</title>
</head>
<body>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">Component Tests</h1>
{buttonVariants.map(variant => (
<div class="mb-6">
<h2 class="text-xl font-semibold mb-4 capitalize">{variant} Buttons</h2>
<div class="space-x-4">
{buttonSizes.map(size => (
<Button variant={variant} size={size}>
{variant} {size}
</Button>
))}
</div>
</div>
))}
</div>
</body>
</html>
สรุป
การสร้าง components ที่ดีด้วย Astro และ TypeScript ต้องคำนึงถึง:
- Type Safety: ใช้ TypeScript interfaces สำหรับ props
- Reusability: สร้าง components ที่ใช้งานได้หลากหลาย
- Consistency: ใช้ design system และ naming conventions ที่สม่ำเสมอ
- Performance: ใช้ CSS classes แทน inline styles
- Accessibility: เพิ่ม ARIA attributes ที่จำเป็น
ด้วยหลักการเหล่านี้ เราสามารถสร้าง component library ที่มีคุณภาพและนำกลับมาใช้ได้อย่างมีประสิทธิภาพ!