SOLID 原则:构建可维护的应用程序

介绍

SOLID 原则在 React 项目中非常重要,有助于创建更易于维护、可扩展和可读的基于组件的应用程序。

让我们通过一些实际的例子看看这些原则如何应用于 React。

1.单一职责原则(SRP)

每个类或函数都应该只做一件事,并做好它,专注于单一职责。

现实生活中的例子

💡 厨师不应该负责烹饪、清洁和管理餐厅。每项任务都应分配给特定的人或团队。

代码示例

// Bad Example (Violating SRP)
// Colocation

 const UserCard: React.FC = () => {
    // Multiple responsibilities:
    // 1. Making API call
    // 2. Handling UI updates
  const [user, setUser] = React.useState({ name: '', age: 0 });
  React.useEffect(() => {
    fetch('/api/user')
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, []);

  return 
{user.name} is {user.age} years old.
; }; // Good Example (Following SRP) // Separate concerns into different modules type UserType={ name: string; age: number } const UserCard: React.FC<{ user: UserType}> = ({ user }) => { return
{user.name} is {user.age} years old.
; }; const fetchUser = async (): Promise => { const response = await fetch('/api/user'); return response.json(); }; // Usage const App: React.FC = () => { const [user, setUser] = React.useState(null); React.useEffect(() => { // This is a mock example, not the exact code. fetchUser().then(setUser); }, []); return user ? :
Loading...
; };

2.开放/封闭原则(OCP)

组件应该对扩展开放,但对修改关闭。

现实生活中的例子

💡 您可以在不改变手机核心功能的情况下向手机添加新应用程序。

代码示例

// Bad Example (Violating OCP)
type NotificationType = 'email' | 'message' | 'alert';

interface NotificationBadgeProps {
  type: NotificationType;
  count: number;
}

const NotificationBadge: React.FC = ({ type, count }) => {
  if (type === 'email') {
    return 
{count} Emails
; } else if (type === 'message') { return
{count} Messages
; } else if (type === 'alert') { return
{count} Alerts
; } return null; }; // Good Example (Following OCP) interface BadgeConfig { className: string; label: string; } const badgeStyles: Record = { email: { className: 'email-badge', label: 'Emails' }, message: { className: 'message-badge', label: 'Messages' }, alert: { className: 'alert-badge', label: 'Alerts' } }; interface ExtendedNotificationBadgeProps { type: string; count: number; } const NotificationBadge: React.FC = ({ type, count }) => { const config = badgeStyles[type]; if (!config) return null; return (
{count} {config.label}
); }; // Here is an example of extending without modifying existing code const extendedBadgeStyles = { ...badgeStyles, social: { className: 'social-badge', label: 'Social' } };

3.里氏替换原则(LSP)

当子组件取代其父组件时,子组件应该能够无缝地工作。

注:里氏替换法由 Barbara Liskov 于 1987 年引入。

现实生活中的例子

💡 想象一下一辆玩具车。如果有人把它换成遥控车,当你推它时它应该仍然能正常工作。

代码示例

// Bad Example (Violating LSP)
const Input: React.FC<{ type: 'text' | 'number' }> = ({ type }) => {
  if (type === 'number') {
    return ;
  }
  return ;
};


// Good Example (Following LSP)
const TextInput: React.FC = () => ;
const NumberInput: React.FC = () => ;

const FormField: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
  
// you will be able to send any children. {children}
); // Usage const App: React.FC = () => ( );

4.接口隔离原则(ISP)

不要强迫组件实现它们不使用的道具或方法。

现实生活中的例子

💡 想象一下餐厅的菜单。如果你点了披萨,你不应该看寿司选项,除非你愿意。

代码示例

将较大的界面拆分为较小、更集中的界面。

// Types for User-related operations
interface User {
  id: number;
  name: string;
  email: string;
}

type FetchState = {
  data: T | null;
  loading: boolean;
  error: Error | null;
};

// Bad Example (Violating ISP)
 interface UserProps {
  name: string;
  age: number;
  onClick: () => void;
}

const UserCard: React.FC = ({ name, age, onClick }) => (
  
{name} is {age} years old.
); // Good Example (Following ISP) first. interface UserInfoProps { name: string; age: number; } interface ClickableProps { onClick: () => void; } const UserInfo: React.FC = ({ name, age }) => (
{name} is {age} years old.
); const Clickable: React.FC = ({ onClick, children }) => (
{children}
); // Usage const App: React.FC = () => ( alert('Clicked!')}> );

或者

interface UserProps {
  name: string;
  age: number;
 // so no more required to add Click handling.
 onClick?: () => void;
}
const UserCard: React.FC = ({ name, age, onClick }) => (
  
{name} is {age} years old.
);

5.依赖倒置原则(DIP)

高级组件应该依赖于抽象,而不是细节。

现实生活中的例子

💡 想象一下一盏灯。开关不应该关心灯是 LED 灯还是荧光灯。它只需要知道它是一个光源。

代码示例 1

并且您还可以使用钩子或上下文来抽象依赖关系。

例子

//Bad Example of (DIP)
const ThemeButton: React.FC = () => {
  const theme = 'dark';
  return ;
};

// Good Example of (DIP)
const useTheme = () => React.useContext(ThemeContext);

const ThemeButton: React.FC = () => {
  const theme = useTheme();
  return ;
};

// Context provider
const ThemeContext = React.createContext('light');
const App: React.FC = () => (
  
    
  
);

结论

在 React 和 TypeScript 中应用 SOLID 原则有助于创造更多:

  • 类型安全组件
  • 可维护且可预测的代码
  • 易于测试的实现
  • 灵活且可扩展的应用程序
  • **专业提示**:使用 TypeScript 的类型系统来执行这些原则。接口和泛型可以帮助创建更强大、更灵活的代码结构。

    请记住,这些原则只是指导方针。慎重应用它们,在过度设计和保持代码整洁易懂之间取得平衡。

    谢谢阅读。