← Назад к вопросам

Как применяются в React принципы SOLID?

2.0 Middle🔥 161 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как применяются в React принципы SOLID

SOLID — это набор пяти принципов проектирования, которые делают код более гибким, поддерживаемым и масштабируемым. В React эти принципы применяются через компоненты, хуки и архитектуру приложения.

1. Single Responsibility Principle (SRP)

Каждый компонент должен иметь только одну причину для изменения.

Компонент отвечает за одну задачу:

// Плохо - компонент делает слишком много
function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [posts, setPosts] = useState([]);
  const [comments, setComments] = useState([]);
  // ... 100 строк кода для загрузки, отрисовки, редактирования
}

// Хорошо - разделяем на компоненты с одной ответственностью
function UserProfile({ userId }) {
  const user = useUser(userId);
  return (
    <div>
      <UserCard user={user} />
      <UserPosts userId={userId} />
      <UserComments userId={userId} />
    </div>
  );
}

function UserCard({ user }) {
  // Отвечает только за отрисовку карточки пользователя
  return <div>{user.name}</div>;
}

function UserPosts({ userId }) {
  // Отвечает только за загрузку и отрисовку постов
  const posts = usePosts(userId);
  return <div>{/* посты */}</div>;
}

function UserComments({ userId }) {
  // Отвечает только за комментарии
  const comments = useComments(userId);
  return <div>{/* комментарии */}</div>;
}

В React это выглядит так:

  • Отделяем логику в custom hooks
  • Каждый компонент отвечает за UI-ячейку
  • Бизнес-логика в отдельных слоях (services, utils)

2. Open/Closed Principle (OCP)

Компоненты открыты для расширения, закрыты для модификации.

Используем props для сделать компоненты гибкими:

// Плохо - нужно менять Button для каждого кейса
function Button() {
  return (
    <button className="px-4 py-2 bg-blue-500 text-white rounded">
      Нажми меня
    </button>
  );
}

// Хорошо - через props расширяем возможности
function Button({
  variant = 'primary',
  size = 'md',
  children,
  onClick,
  disabled = false,
  ...rest
}) {
  const buttonClass = cn(
    'px-4 py-2 rounded font-medium transition-colors',
    {
      'bg-blue-500 text-white': variant === 'primary',
      'bg-gray-200 text-gray-800': variant === 'secondary',
      'bg-red-500 text-white': variant === 'danger',
    },
    {
      'px-2 py-1 text-sm': size === 'sm',
      'px-4 py-2 text-base': size === 'md',
      'px-6 py-3 text-lg': size === 'lg',
    },
    disabled && 'opacity-50 cursor-not-allowed'
  );
  
  return (
    <button
      className={buttonClass}
      onClick={onClick}
      disabled={disabled}
      {...rest}
    >
      {children}
    </button>
  );
}

// Использование — расширяем без изменения Button
<Button variant="primary" size="lg">Сохранить</Button>
<Button variant="danger" size="sm">Удалить</Button>
<Button variant="secondary" disabled>Загрузка...</Button>

Применение OCP:

  • Props для конфигурации
  • Composition через children
  • Render props и HOC
  • Hooks для переиспользуемой логики

3. Liskov Substitution Principle (LSP)

Дочерние компоненты должны заменяться на родительские без нарушения работы.

// Интерфейс Button
function PrimaryButton(props) {
  return <Button variant="primary" {...props} />;
}

// Интерфейс Button
function SecondaryButton(props) {
  return <Button variant="secondary" {...props} />;
}

// Можем использовать их взаимозаменяемо
function FormAction({ actionType }) {
  const ActionButton = actionType === 'save' ? PrimaryButton : SecondaryButton;
  
  return (
    <ActionButton onClick={handleAction}>
      {actionType === 'save' ? 'Сохранить' : 'Отменить'}
    </ActionButton>
  );
}

В контексте хуков:

// Все эти хуки возвращают одинаковый интерфейс
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => { /* логика */ }, [url]);
  
  return { data, loading, error };
}

function useMockData(url) {
  return { data: mockData[url], loading: false, error: null };
}

// Взаимозаменяемы
function UserComponent() {
  // В продакшене используем useApi
  const { data: user, loading, error } = useApi('/api/user');
  
  // В тестах используем useMockData
  // const { data: user, loading, error } = useMockData('/api/user');
  
  if (loading) return <Loading />;
  if (error) return <Error />;
  return <User data={user} />;
}

4. Interface Segregation Principle (ISP)

Компонент должен зависеть от минимального набора props.

// Плохо - большой интерфейс
function UserCard({ 
  user, 
  onEdit, 
  onDelete, 
  onShare,
  onFollowClick,
  onMessageClick,
  onReportClick,
  isAdmin,
  isDarkMode,
  showBadge,
  // ... 20 пропсов
}) {
  return <div>{user.name}</div>;
}

// Хорошо - разбиваем на меньшие компоненты
function UserCard({ user, onAction }) {
  return (
    <div>
      <UserInfo user={user} />
      <UserActions user={user} onAction={onAction} />
    </div>
  );
}

function UserInfo({ user }) {
  // Нужен только user
  return <div>{user.name}</div>;
}

function UserActions({ user, onAction }) {
  // Нужны только user и callback
  return (
    <>
      <button onClick={() => onAction('edit')}>Редактировать</button>
      <button onClick={() => onAction('delete')}>Удалить</button>
    </>
  );
}

В хуках:

// Плохо - возвращает всё подряд
function useUser(userId) {
  return {
    user,
    loading,
    error,
    refetch,
    update,
    delete: deleteUser,
    block,
    report,
    // ... 15 методов
  };
}

// Хорошо - раздельные хуки
function useUserData(userId) {
  return { user, loading, error };
}

function useUserMutation(userId) {
  return { update, delete: deleteUser };
}

function useModerationActions(userId) {
  return { block, report };
}

// Компонент берет только нужное
function AdminPanel({ userId }) {
  const { user } = useUserData(userId);
  const { block, report } = useModerationActions(userId);
  return <div>{/* модерация */}</div>;
}

5. Dependency Inversion Principle (DIP)

Зависеть от абстракций, не от конкретных реализаций.

// Плохо - зависим от конкретного класса
function UserComponent() {
  const authService = new AuthService();
  const user = authService.getUser();
  return <div>{user.name}</div>;
}

// Хорошо - инъекция зависимости
function UserComponent({ authService }) {
  const user = authService.getUser();
  return <div>{user.name}</div>;
}

// Еще лучше - через контекст
const AuthContext = createContext();

function App() {
  const authService = useMemo(() => new AuthService(), []);
  
  return (
    <AuthContext.Provider value={authService}>
      <UserComponent />
    </AuthContext.Provider>
  );
}

function UserComponent() {
  const authService = useContext(AuthContext);
  const user = authService.getUser();
  return <div>{user.name}</div>;
}

С хуками:

// Абстрактный хук (интерфейс)
function useUserService() {
  return useContext(UserServiceContext);
}

// Конкретная реализация для продакшена
class RealUserService {
  getUser = async (id) => fetch(`/api/users/${id}`).then(r => r.json());
}

// Конкретная реализация для тестов
class MockUserService {
  getUser = async (id) => mockUsers[id];
}

// Компонент не знает, какая реализация
function UserProfile() {
  const userService = useUserService();
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    userService.getUser(id).then(setUser);
  }, [id, userService]);
  
  return <div>{user?.name}</div>;
}

Практическое применение в проекте

// Структура, соответствующая SOLID
src/
├── components/          // UI компоненты (SRP + OCP)
│   ├── ui/
│   │   ├── Button.tsx  // Один компонент = одна ответственность
│   │   └── Input.tsx
│   └── features/
│       └── UserProfile.tsx
├── hooks/              // Абстракции логики
│   ├── useAuth.ts      // Зависит от AuthService
│   └── useUser.ts
├── services/           // Конкретные реализации
│   ├── authService.ts
│   └── userService.ts
├── contexts/           // Инъекция зависимостей
│   └── ServiceContext.tsx
└── types/              // Интерфейсы
    └── index.ts

Итог

SOLID в React — это архитектурные паттерны, которые делают код:

  • Проще для понимания
  • Легче для тестирования
  • Проще для расширения
  • Более переиспользуемым
  • Менее подверженным ошибкам

Ключевые инструменты в React для SOLID:

  • Компоненты для SRP
  • Props для OCP
  • Context + Hooks для DIP
  • Custom hooks для LSP и ISP
Как применяются в React принципы SOLID? | PrepBro