DEV Community

Cinderella_n
Cinderella_n

Posted on

Organizing the Frontend project's components folder

การจัดโครงสร้างในโฟลเดอร์ 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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง:

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง:

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง:

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง:

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;
Enter fullscreen mode Exit fullscreen mode

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',
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ตัวอย่าง:

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}>
          &times;
        </button>
        <div className={styles.modalContent}>{children}</div>
      </div>
    </div>
  );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Factors to Consider When Structuring Components

  1. Reusability: ควรแยก component ที่สามารถใช้ซ้ำได้ออกจาก component ที่เฉพาะเจาะจงกับฟีเจอร์
  2. Maintainability: การจัดโครงสร้างที่ดีช่วยให้การดูแลรักษาและการอัพเดตโปรเจคเป็นไปอย่างราบรื่น
  3. Scalability: โครงสร้างที่ดีจะช่วยให้การขยายฟีเจอร์และการเพิ่ม component ใหม่ ๆ เป็นเรื่องง่าย
  4. 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)

Collapse
 
sunstar2090 profile image
sunstar

Thank you for the great article. I love its accuracy and richness.