การจัดโครงสร้างในโฟลเดอร์ components
ของโปรเจค Frontend เป็นสิ่งที่สำคัญ เพราะมันช่วยให้การพัฒนาและดูแลรักษาโปรเจคง่ายขึ้น โดยเฉพาะเมื่อต้องจัดการกับ component ที่มีขนาดใหญ่หรือมีหลาย component ที่ซับซ้อน การจัดระเบียบโฟลเดอร์จะช่วยให้โค้ดเป็นระเบียบและสามารถค้นหาและเข้าใจได้ง่ายขึ้น
ต่อไปนี้คือโครงสร้างโฟลเดอร์ components
ในรูปแบบต่างๆ ที่นิยมใช้กันในโปรเจคที่พัฒนาโดยใช้ Next.js และ TypeScript:
1. Atomic Design Structure
Atomic Design เป็นแนวคิดการออกแบบที่แบ่ง component ตามความซับซ้อนและการใช้งาน โดยจะถูกแบ่งเป็น 5 ระดับ: Atoms, Molecules, Organisms, Templates, และ Pages.
src/
└── components/
├── atoms/ # Small, reusable elements (e.g., buttons, inputs)
│ ├── Button.tsx
│ ├── Input.tsx
│ ├── Icon.tsx
│ └── ... # Additional atoms
│
├── molecules/ # Combinations of atoms (e.g., form groups)
│ ├── FormInput.tsx
│ ├── NavLink.tsx
│ └── ... # Additional molecules
│
├── organisms/ # Complex UI components (e.g., headers, cards)
│ ├── Header.tsx
│ ├── Card.tsx
│ ├── Footer.tsx
│ └── ... # Additional organisms
│
├── templates/ # Page templates (layouts with placeholders)
│ ├── MainLayout.tsx
│ ├── DashboardLayout.tsx
│ └── ... # Additional templates
│
└── pages/ # Page-specific components (used directly in pages)
├── HomePage.tsx
├── AboutPage.tsx
└── ... # Additional page components
ตัวอย่าง:
Atoms: Button.tsx
import React from 'react';
interface ButtonProps {
label: string;
onClick: () => void;
type?: 'button' | 'submit' | 'reset';
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({ label, onClick, type = 'button', disabled = false }) => (
<button type={type} onClick={onClick} disabled={disabled} className="btn">
{label}
</button>
);
export default Button;
Molecules: FormInput.tsx
import React from 'react';
import Input from '../atoms/Input';
import Label from '../atoms/Label';
interface FormInputProps {
label: string;
value: string;
onChange: (value: string) => void;
}
const FormInput: React.FC<FormInputProps> = ({ label, value, onChange }) => (
<div className="form-input">
<Label text={label} />
<Input value={value} onChange={onChange} />
</div>
);
export default FormInput;
Organisms: Header.tsx
import React from 'react';
import NavLink from '../molecules/NavLink';
import Logo from '../atoms/Logo';
const Header: React.FC = () => (
<header className="header">
<Logo />
<nav>
<NavLink href="/" label="Home" />
<NavLink href="/about" label="About" />
<NavLink href="/contact" label="Contact" />
</nav>
</header>
);
export default Header;
2. Feature-Based Structure
โครงสร้างที่แยก component ตามฟีเจอร์หรือโมดูลเป็นที่นิยมในโปรเจคที่มีฟีเจอร์หลากหลาย ช่วยให้การจัดการและขยายฟีเจอร์เป็นไปอย่างมีประสิทธิภาพ
src/
└── components/
├── authentication/ # Components related to authentication
│ ├── Login.tsx
│ ├── Signup.tsx
│ └── PasswordReset.tsx
│
├── dashboard/ # Components specific to the dashboard
│ ├── DashboardHeader.tsx
│ ├── DashboardSidebar.tsx
│ └── StatsCard.tsx
│
├── userProfile/ # Components for user profile
│ ├── ProfileHeader.tsx
│ ├── EditProfileForm.tsx
│ └── Avatar.tsx
│
├── shared/ # Shared or common components across features
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── ... # Additional shared components
│
└── layout/ # Layout components
├── Header.tsx
├── Footer.tsx
└── Sidebar.tsx
ตัวอย่าง:
Authentication: Login.tsx
import React, { useState } from 'react';
import Button from '../shared/Button';
import FormInput from '../shared/FormInput';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = () => {
// Logic for login
};
return (
<div className="login">
<h2>Login</h2>
<FormInput label="Email" value={email} onChange={setEmail} />
<FormInput label="Password" value={password} onChange={setPassword} />
<Button label="Login" onClick={handleLogin} />
</div>
);
};
export default Login;
Dashboard: StatsCard.tsx
import React from 'react';
interface StatsCardProps {
title: string;
value: number;
icon: React.ReactNode;
}
const StatsCard: React.FC<StatsCardProps> = ({ title, value, icon }) => (
<div className="stats-card">
<div className="stats-card-icon">{icon}</div>
<div className="stats-card-info">
<h3>{title}</h3>
<p>{value}</p>
</div>
</div>
);
export default StatsCard;
3. Domain-Driven Structure
โครงสร้างนี้จะเน้นการจัดระเบียบ component ตาม domain หรือ bounded context ในโปรเจคของคุณ ทำให้โครงสร้างนี้เหมาะกับระบบที่มีความซับซ้อนและต้องการการแยก domain ชัดเจน
src/
└── components/
├── domain/
│ ├── product/ # Components related to product domain
│ │ ├── ProductCard.tsx
│ │ ├── ProductList.tsx
│ │ └── ProductDetail.tsx
│ │
│ ├── cart/ # Components for cart domain
│ │ ├── CartItem.tsx
│ │ ├── CartSummary.tsx
│ │ └── CartIcon.tsx
│ │
│ ├── user/ # Components for user domain
│ │ ├── UserAvatar.tsx
│ │ ├── UserProfile.tsx
│ │ └── UserSettings.tsx
│ │
│ └── ... # Additional domain-specific components
│
├── ui/ # UI elements (atoms, molecules, etc.)
│ ├── atoms/
│ ├── molecules/
│ └── organisms/
│
└── layout/ # Layout components
├── Header.tsx
├── Footer.tsx
└── Sidebar.tsx
ตัวอย่าง:
Product: ProductCard.tsx
import React from 'react';
interface ProductCardProps {
name: string;
price: number;
imageUrl: string;
onAddToCart: () => void;
}
const ProductCard: React.FC<ProductCardProps> = ({ name, price, imageUrl, onAddToCart }) => (
<div className="product-card">
<img src={imageUrl} alt={name} className="product-card-image" />
<div className="product-card-info">
<h3>{name}</h3>
<p>${price.toFixed(2)}</p>
<button onClick={onAddToCart}>Add to Cart</button>
</div>
</div>
);
export default ProductCard;
Cart: CartSummary.tsx
import React from 'react';
interface CartSummaryProps {
totalItems: number;
totalPrice: number;
}
const CartSummary: React.FC<CartSummary
Props> = ({ totalItems, totalPrice }) => (
<div className="cart-summary">
<h3>Cart Summary</h3>
<p>Total Items: {totalItems}</p>
<p>Total Price: ${totalPrice.toFixed(2)}</p>
<button>Checkout</button>
</div>
);
export default CartSummary;
4. Component-Driven Development (CDD) with Storybook
โครงสร้างนี้ออกแบบมาเพื่อรองรับการพัฒนาแบบ Component-Driven Development (CDD) โดยใช้ Storybook ซึ่งช่วยให้คุณสามารถพัฒนาและทดสอบ component ในรูปแบบที่แยกจากแอปพลิเคชันหลัก
src/
└── components/
├── Button/
│ ├── Button.tsx # Component implementation
│ ├── Button.stories.tsx # Storybook stories
│ ├── Button.test.tsx # Unit tests
│ └── Button.module.css # Component-specific styles
│
├── Input/
│ ├── Input.tsx
│ ├── Input.stories.tsx
│ ├── Input.test.tsx
│ └── Input.module.css
│
├── Modal/
│ ├── Modal.tsx
│ ├── Modal.stories.tsx
│ ├── Modal.test.tsx
│ └── Modal.module.css
│
└── ... # Additional component folders
ตัวอย่าง:
Button: Button.tsx
import React from 'react';
import styles from './Button.module.css';
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
const Button: React.FC<ButtonProps> = ({ label, onClick, variant = 'primary' }) => (
<button className={`${styles.btn} ${styles[variant]}`} onClick={onClick}>
{label}
</button>
);
export default Button;
Button: Button.stories.tsx (Storybook)
import React from 'react';
import { Meta, Story } from '@storybook/react';
import Button, { ButtonProps } from './Button';
export default {
title: 'Components/Button',
component: Button,
} as Meta;
const Template: Story<ButtonProps> = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = {
label: 'Primary Button',
onClick: () => console.log('Primary Button Clicked'),
variant: 'primary',
};
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Secondary Button',
onClick: () => console.log('Secondary Button Clicked'),
variant: 'secondary',
};
5. Shared Component Library
ในโปรเจคที่มีหลายทีมทำงานร่วมกัน การสร้างโครงสร้างที่ใช้ component ร่วมกันเป็นสิ่งที่สำคัญ โครงสร้างนี้เน้นการแยก component ที่สามารถใช้ซ้ำได้ทั่วโปรเจค
src/
└── components/
├── shared/ # Shared components across the application
│ ├── Button/
│ │ ├── Button.tsx
│ │ └── Button.module.css
│ │
│ ├── Modal/
│ │ ├── Modal.tsx
│ │ └── Modal.module.css
│ │
│ └── ... # Additional shared components
│
├── featureSpecific/ # Feature-specific components
│ ├── UserProfile/
│ │ ├── ProfileHeader.tsx
│ │ ├── ProfileDetails.tsx
│ │ └── Avatar.tsx
│ │
│ ├── ProductList/
│ │ ├── ProductCard.tsx
│ │ └── ProductFilter.tsx
│ │
│ └── ... # Additional feature-specific components
│
└── layout/ # Layout components
├── Header.tsx
├── Footer.tsx
└── Sidebar.tsx
ตัวอย่าง:
Shared: Modal.tsx
import React from 'react';
import styles from './Modal.module.css';
interface ModalProps {
title: string;
isOpen: boolean;
onClose: () => void;
}
const Modal: React.FC<ModalProps> = ({ title, isOpen, onClose, children }) => {
if (!isOpen) return null;
return (
<div className={styles.modalOverlay}>
<div className={styles.modal}>
<h2>{title}</h2>
<button className={styles.closeButton} onClick={onClose}>
×
</button>
<div className={styles.modalContent}>{children}</div>
</div>
</div>
);
};
export default Modal;
Feature-Specific: ProfileHeader.tsx
import React from 'react';
interface ProfileHeaderProps {
name: string;
bio: string;
avatarUrl: string;
}
const ProfileHeader: React.FC<ProfileHeaderProps> = ({ name, bio, avatarUrl }) => (
<div className="profile-header">
<img src={avatarUrl} alt={name} className="profile-avatar" />
<h1>{name}</h1>
<p>{bio}</p>
</div>
);
export default ProfileHeader;
Factors to Consider When Structuring Components
- Reusability: ควรแยก component ที่สามารถใช้ซ้ำได้ออกจาก component ที่เฉพาะเจาะจงกับฟีเจอร์
- Maintainability: การจัดโครงสร้างที่ดีช่วยให้การดูแลรักษาและการอัพเดตโปรเจคเป็นไปอย่างราบรื่น
- Scalability: โครงสร้างที่ดีจะช่วยให้การขยายฟีเจอร์และการเพิ่ม component ใหม่ ๆ เป็นเรื่องง่าย
- Performance: ใช้เทคนิคที่เหมาะสมในการโหลดและใช้ component เพื่อให้แน่ใจว่าแอปพลิเคชันของคุณมีประสิทธิภาพ
Best Practices for Component Structure
- Single Responsibility Principle: แต่ละ component ควรทำหน้าที่เดียวและทำได้ดี
- Component Naming: ตั้งชื่อ component ให้สื่อความหมายและชัดเจน
- Component Composition: ใช้ composition แทน inheritance เมื่อสร้าง component ใหม่
- Use Prop Types or TypeScript: กำหนด prop types หรือใช้ TypeScript interfaces เพื่อเพิ่มความปลอดภัยในการใช้งาน
- Write Tests: เขียน unit tests สำหรับ component ทุกตัวเพื่อตรวจสอบการทำงาน
ด้วยข้อมูลและแนวทางเหล่านี้ หวังว่าคุณจะสามารถจัดโครงสร้างในโฟลเดอร์ components
ของโปรเจคได้อย่างมีประสิทธิภาพและเหมาะสมกับความต้องการของโปรเจคของคุณ!
Top comments (1)
Thank you for the great article. I love its accuracy and richness.