Best practices để áp dụng S.O.L.I.D trong React

3 min read

1. Single Responsibility Principle (SRP)

Mỗi component chỉ nên có một nhiệm vụ duy nhất

Example: User Profile Component

Nên:

  • Chia các component nhỏ, ứng với từng chức năng
// UserProfile.js
const UserProfile = ({ user }) => {
  return (
    <div>
      <UserAvatar user={user} />
      <UserInfo user={user} />
    </div>
  );
};

// UserAvatar.js
const UserAvatar = ({ user }) => {
  return <img src={user.avatarUrl} alt={`${user.name}'s avatar`} />;
};

// UserInfo.js
const UserInfo = ({ user }) => {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
    </div>
  );
};

Không nên:

  • Gộp hết việc hiển thị, lấy dữ liệu và xử lý logic nghiệp vụ vào trong cùng một component.
// IncorrectUserProfile.js
const IncorrectUserProfile = ({ user }) => {
  // Fetching data, handling business logic and displaying all in one
  const handleEdit = () => {
    console.log("Edit user");
  };

  return (
    <div>
      <img src={user.avatarUrl} alt={`${user.name}'s avatar`} />
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
      <button onClick={handleEdit}>Edit User</button>
    </div>
  );
};

2. Open/Closed Principle (OCP)

Các component có thể được mở rộng mà không thay đổi phần cốt lõi, cho phép thêm chức năng mới mà không ảnh hưởng đến code hiện có (open for extension, closed for modification).

Example: Themable Button

Nên:

  • Sử dụng props để mở rộng các chức năng của component mà không làm thay đổi component gốc.
// Button.js
const Button = ({ onClick, children, style }) => {
  return (
    <button onClick={onClick} style={style}>
      {children}
    </button>
  );
};

// Usage
const PrimaryButton = (props) => {
  const primaryStyle = { backgroundColor: 'blue', color: 'white' };
  return <Button {...props} style={primaryStyle} />;
};

Không nên:

  • Sửa đổi component gốc để thêm trực tiếp styles hoặc behaviors.
// IncorrectButton.js
// Modifying the original Button component directly for a specific style
const Button = ({ onClick, children, primary }) => {
  const style = primary ? { backgroundColor: 'blue', color: 'white' } : null;
  return (
    <button onClick={onClick} style={style}>
      {children}
    </button>
  );
};

3. Liskov Substitution Principle (LSP)

Các đối tượng thuộc superclass có thể được thay thế bởi các đối tượng thuộc subclasses của nó mà không gây ra lỗi.

Example: Basic Button and Icon Button

Nên:

  • Đảm bảo subclass components có thể thay thế superclass components mà không gây lỗi.
// BasicButton.js
const BasicButton = ({ onClick, children }) => {
  return <button onClick={onClick}>{children}</button>;
};

// IconButton.js
const IconButton = ({ onClick, icon, children }) => {
  return (
    <button onClick={onClick}>
      <img src={icon} alt="icon" />
      {children}
    </button>
  );
};

Không nên:

  • Thêm các subclass-specific properties mà có thể gây lỗi khi thay thế.
// IncorrectIconButton.js
// This button expects an icon and does not handle the absence of one, breaking when used as a BasicButton
const IncorrectIconButton = ({ onClick, icon }) => {
  if (!icon) {
    throw new Error("Icon is required");
  }
  return (
    <button onClick={onClick}>
      <img src={icon} alt="icon" />
    </button>
  );
};

4. Interface Segregation Principle (ISP)

Các component không nên phụ thuộc vào các interface/methods mà chúng không sử dụng.

Example: Text Component

Nên:

  • Cung cấp các interface cụ thể cho các mục đích sử dụng khác nhau
// Text.js
const Text = ({ type, children }) => {
  switch (type) {
    case 'header':
      return <h1>{children}</h1>;
    case 'title':
      return <h2>{children}</h2>;
    default:
      return <p>{children}</p>;
  }
};

Không nên:

  • Nhồi nhét nhiều properties không cần thiết vào cùng một component.
// IncorrectText.js
// This component expects multiple unrelated props, cluttering the interface
const IncorrectText = ({ type, children, onClick, isLoggedIn }) => {
  if (isLoggedIn && onClick) {
    return <a href="#" onClick={onClick}>{children}</a>;
  }
  return type === 'header' ? <h1>{children}</h1> : <p>{children}</p>;
};

5. Dependency Inversion Principle (DIP)

Các module cấp cao không nên phụ thuộc vào các module cấp thấp, mà cả hai nên phụ thuộc vào các abstractions.

Example: Data Fetching

Nên:

  • Sử dụng hooks hoặc các patterns tương tự để abstract việc fetch data và quản lý state.
// useUserData.js (Abstraction)
const useUserData = (userId) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchData(userId).then(setUser);
  }, [userId]);

  return user;
};

// UserProfile.js
const UserProfile = ({ userId }) => {
  const user = useUserData(userId);

  if (!user) return <p>Loading...</p>;
  return <div><h1>{user.name}</h1></div>;
};

Không nên:

  • Hard-code data fetching trong component.
// IncorrectUserProfile.js
const IncorrectUserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Fetching data directly inside the component
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(setUser);
  }, [userId]);

  if (!user) return <p>Loading...</p>;
  return <div><h1>{user.name}</h1></div>;
};

References

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *