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

Как связать в один проект Frontend на React, менеджер состояния, GraphQL и гексагональный сервер на Backend?

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

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

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

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

Как связать Frontend на React, менеджер состояния, GraphQL и гексагональный сервер на Backend?

Архитектурная картина

Это вопрос о связывании нескольких компонентов системы в единое целое. Давай разберём, как они взаимодействуют.

1. Слой API (Communication)

Инициализация GraphQL клиента:

// lib/apollo-client.ts
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: process.env.REACT_APP_GRAPHQL_URL || 'http://localhost:4000/graphql',
  credentials: 'include', // Отправляем cookies
});

export const apolloClient = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: { fetchPolicy: 'cache-first' },
    query: { fetchPolicy: 'cache-first' },
  },
});

2. Слой State Management (Zustand)

Zustand пример (более лёгкий):

// store/userStore.ts
import { create } from 'zustand';
import { apolloClient } from '@/lib/apollo-client';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserStore {
  user: User | null;
  loading: boolean;
  error: string | null;
  fetchUser: (id: string) => Promise<void>;
  updateUser: (id: string, input: Partial<User>) => Promise<void>;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  loading: false,
  error: null,

  fetchUser: async (id) => {
    set({ loading: true, error: null });
    try {
      const response = await apolloClient.query({
        query: GET_USER,
        variables: { id },
      });
      set({ user: response.data.user, loading: false });
    } catch (error: any) {
      set({ error: error.message, loading: false });
    }
  },

  updateUser: async (id, input) => {
    set({ loading: true });
    try {
      const response = await apolloClient.mutate({
        mutation: UPDATE_USER,
        variables: { id, input },
      });
      set({ user: response.data.updateUser, loading: false });
    } catch (error: any) {
      set({ error: error.message, loading: false });
    }
  },
}));

3. Пользовательский слой (React компоненты)

Компонент с использованием state manager:

// components/UserProfile.tsx
import { useEffect } from 'react';
import { useUserStore } from '@/store/userStore';

interface UserProfileProps {
  userId: string;
}

export function UserProfile({ userId }: UserProfileProps) {
  const { user, loading, fetchUser, updateUser } = useUserStore();

  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  if (loading) return <div>Загрузка...</div>;
  if (!user) return <div>Пользователь не найден</div>;

  const handleUpdateName = async (newName: string) => {
    await updateUser(userId, { name: newName });
  };

  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={() => handleUpdateName('Новое имя')}>
        Изменить имя
      </button>
    </div>
  );
}

4. Backend (Гексагональный сервер)

GraphQL Resolver подключается к Domain через Use Cases:

// resolvers/user.resolver.ts
import { GetUserUseCase } from '@/application/use-cases/get-user.use-case';

export const userResolvers = {
  Query: {
    user: async (_, { id }, context) => {
      const getUserUseCase = context.container.get(GetUserUseCase);
      return await getUserUseCase.execute(id);
    },
  },
};

Use Case слой (бизнес-логика):

// application/use-cases/get-user.use-case.ts
import { UserRepository } from '@/domain/repositories/user.repository';
import { User } from '@/domain/entities/user';

export class GetUserUseCase {
  constructor(private userRepository: UserRepository) {}

  async execute(id: string): Promise<User> {
    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new Error('User not found');
    }
    return user;
  }
}

Domain Layer (независимая от frameworks):

// domain/entities/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

// domain/repositories/user.repository.ts
export interface UserRepository {
  findById(id: string): Promise<User | null>;
  update(id: string, data: Partial<User>): Promise<User>;
}

5. Полный поток данных

  1. Пользователь кликает кнопку в React Component
  2. Вызывается updateUser из store
  3. Store отправляет GraphQL Mutation через Apollo Client
  4. Backend получает GraphQL запрос
  5. Resolver вызывает UpdateUserUseCase
  6. UseCase изменяет Domain Entity через Repository
  7. Repository выполняет SQL запрос
  8. БД возвращает данные
  9. Данные идут обратно: Repository -> UseCase -> Resolver -> Apollo -> Store -> Component
  10. UI обновляется

Ключевые принципы интеграции

1. Разделение ответственности

  • Компоненты отвечают за UI
  • Store отвечает за состояние
  • Apollo отвечает за сетевой слой
  • Backend разделён на слои (Domain, Use Cases, Infrastructure)

2. Однонаправленный поток данных

  • Компонент -> Store -> Apollo -> Backend
  • Backend -> Apollo -> Store -> Компонент

3. Гексагональная архитектура на Backend

  • Domain слой (независимый от фреймворков)
  • Use Cases (бизнес-логика)
  • Infrastructure (реализация репозиториев)
  • GraphQL Resolver (порт в мир)

Инициализация приложения

import { ApolloProvider } from '@apollo/client';
import { apolloClient } from '@/lib/apollo-client';

export function App() {
  return (
    <ApolloProvider client={apolloClient}>
      <UserProfile userId="123" />
    </ApolloProvider>
  );
}

Типичная ошибка

Неправильно - логика в компоненте:

function UserProfile() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch('/api/user').then(setUser);
  }, []);
}

Правильно - логика в store, компонент просто использует:

function UserProfile() {
  const user = useUserStore(s => s.user);
  useEffect(() => {
    useUserStore.getState().fetchUser('123');
  }, []);
}

Заключение

Интеграция всех компонентов - это вопрос разделения ответственности. Каждый слой:

  • Знает только о слое ниже
  • Не зависит от слоев сверху
  • Имеет чётко определённые обязанности

Это обеспечивает масштабируемость, тестируемость и простоту поддержки кода.