SOLID trong React: Hiểu và Áp dụng 5 Nguyên Tắc Thiết Kế Phần Mềm
🚒

SOLID trong React: Hiểu và Áp dụng 5 Nguyên Tắc Thiết Kế Phần Mềm

Mario Sanchez

Tác giả: Phi Long

Cập nhật lần cuối: 04/03/2025

SOLID là tập hợp 5 nguyên tắc thiết kế phần mềm (do Robert C. Martin đề xuất)


1. S - Single Responsibility Principle (Nguyên tắc trách nhiệm đơn nhất)

Ý nghĩa: Mỗi module, class, hoặc function chỉ nên có một lý do duy nhất để thay đổi. Nói cách khác, một thành phần chỉ nên làm một việc.

Áp dụng trong React:

  • Tách logic ra khỏi UI. Ví dụ, thay vì nhồi nhét cả giao diện lẫn logic xử lý dữ liệu trong một component, bạn nên chia nhỏ:
  • UI Component: Chỉ hiển thị giao diện (dumb/presentational component).
  • Container/Hook: Xử lý logic (fetch API, state management).
  • Ví dụ:
  • javascript
    function UserProfile() {
      const [user, setUser] = useState(null);
      useEffect(() => {
        fetch('/api/user').then(res => setUser(res.data));
      }, []);
      return <div>{user?.name}</div>;
    }
    // Nên:
    function useUserData() { // Hook xử lý logic
      const [user, setUser] = useState(null);
      useEffect(() => {
        fetch('/api/user').then(res => setUser(res.data));
      }, []);
      return user;
    }
    function UserProfile() { // Component chỉ hiển thị
      const user = useUserData();
      return <div>{user?.name}</div>;
    }
  • Lợi ích: Nếu logic fetch API thay đổi, bạn chỉ sửa useUserData, không động đến UI.

  • 2. O - Open/Closed Principle (Nguyên tắc mở/đóng)

    Ý nghĩa: Các thành phần nên mở để mở rộng (thêm tính năng) nhưng đóng để sửa đổi (không cần thay đổi code cũ).

    Áp dụng trong React:

  • Sử dụng props, composition, hoặc higher-order components (HOC) để mở rộng chức năng mà không sửa code gốc.
  • Ví dụ: Một button có thể mở rộng để hỗ trợ nhiều kiểu mà không sửa component chính:
  • javascript
    function Button({ variant = 'primary', children, ...props }) {
      const className = variant === 'primary' ? 'btn-primary' : 'btn-secondary';
      return <button className={className} {...props}>{children}</button>;
    }
    // Sử dụng
    <Button variant="primary">Click me</Button>
    <Button variant="secondary" disabled>Disabled</Button>

  • Nếu cần thêm kiểu button mới (ví dụ: danger), chỉ cần thêm logic trong variant, không sửa cấu trúc code cũ.
  • Bonus: Có thể dùng React Context để truyền cấu hình xuống các component con mà không sửa chúng.

  • 3. L - Liskov Substitution Principle (Nguyên tắc thay thế Liskov)

    Ý nghĩa: Một đối tượng của lớp con có thể thay thế cho đối tượng của lớp cha mà không làm hỏng chương trình. Trong React, điều này áp dụng cho việc tái sử dụng component.

    Áp dụng trong React:

  • Đảm bảo các component con (hoặc biến thể) có thể thay thế component cha mà không gây lỗi.
  • Ví dụ: Nếu bạn có một component Card cơ bản: Và một biến thể ImageCard: Cả hai nên có thể dùng trong cùng một context (ví dụ: danh sách card) mà không cần thay đổi code gọi chúng:
  • javascript
    function Card({ title, content }) {
      return (
        <div className="card">
          <h2>{title}</h2>
          <p>{content}</p>
        </div>
      );
    }

    javascript
    function ImageCard({ title, content, imageUrl }) {
      return (
        <div className="card">
          <h2>{title}</h2>
          <img src={imageUrl} alt={title} />
          <p>{content}</p>
        </div>
      );
    }
    javascript
    <CardList cards={[<Card />, <ImageCard />]} />
  • Lưu ý: Đảm bảo props cơ bản (như title, content) luôn nhất quán.

  • 4. I - Interface Segregation Principle (Nguyên tắc phân tách giao diện)

    Ý nghĩa: Không bắt buộc một thành phần phải phụ thuộc vào những thứ nó không dùng.

    Áp dụng trong React:

  • Giữ props của component tối giản, chỉ truyền những gì cần thiết.
  • Ví dụ: Tránh truyền toàn bộ object lớn khi chỉ cần vài thuộc tính:
  • javascript
    // Tránh:
    function UserAvatar({ user }) {
      return <img src={user.avatarUrl} alt={user.name} />;
    }
    <UserAvatar user={{ id: 1, name: 'John', avatarUrl: 'url', email: 'john@example.com' }} />
    
    // Nên:
    function UserAvatar({ avatarUrl, name }) {
      return <img src={avatarUrl} alt={name} />;
    }
    <UserAvatar avatarUrl="url" name="John" />
  • Sử dụng custom hooks để tách logic không liên quan ra khỏi component, tránh nhồi nhét mọi thứ vào một nơi.

  • 5. D - Dependency Inversion Principle (Nguyên tắc đảo ngược phụ thuộc)

    Ý nghĩa: Các module cấp cao không nên phụ thuộc vào module cấp thấp, mà cả hai nên phụ thuộc vào abstraction (trừu tượng).

    Áp dụng trong React:

  • Sử dụng dependency injection hoặc context/hooks để giảm sự phụ thuộc trực tiếp vào các thư viện hoặc API cụ thể.
  • Ví dụ: Thay vì gọi trực tiếp một API trong component: Hãy tạo abstraction qua hook:
  • javascript
    // Tránh:
    function UserList() {
      const [users, setUsers] = useState([]);
      useEffect(() => {
        fetch('/api/users').then(res => setUsers(res.data));
      }, []);
      return <ul>{users.map(u => <li>{u.name}</li>)}</ul>;
    }

    javascript
    function useApiFetch(url) {
      const [data, setData] = useState(null);
      useEffect(() => {
        fetch(url).then(res => setData(res.data));
      }, [url]);
      return data;
    }
    
    function UserList() {
      const users = useApiFetch('/api/users');
      return <ul>{users?.map(u => <li>{u.name}</li>)}</ul>;
    }
  • Lợi ích: Nếu sau này bạn đổi sang dùng Axios hoặc GraphQL, chỉ cần sửa useApiFetch, không cần đụng đến UserList
  • Bình luận(0)

    Hãy là người đầu tiên bình luận!