Урок 12

Структура React-приложения

Хорошая структура проекта упрощает навигацию и поддержку. Универсального правила нет, но есть проверенные подходы.

Базовая структура

src/
├── components/     # переиспользуемые компоненты
├── pages/          # страницы (если есть роутинг)
├── hooks/          # кастомные хуки
├── utils/          # вспомогательные функции
├── api/            # запросы к серверу
├── types/          # общие типы
├── App.tsx
└── main.tsx

По типам vs по фичам

По типам — файлы группируются по техническому назначению:

components/
  Button.tsx
  Card.tsx
  Modal.tsx
hooks/
  useAuth.ts
  useFetch.ts

Плюс: понятно, что где лежит. Минус: при росте проекта компоненты одной фичи разбросаны.

По фичам — группировка по функциональности:

features/
  auth/
    components/
      LoginForm.tsx
    hooks/
      useAuth.ts
    api/
      authApi.ts
    types.ts
  posts/
    components/
      PostCard.tsx
    hooks/
      usePosts.ts
    api/
      postsApi.ts
    types.ts

Плюс: всё по фиче в одном месте. Минус: дублирование папок.

Компоненты

Именование: PascalCase.tsx.

components/
  Button/
    Button.tsx
    Button.module.css
    index.ts
  Card/
    Card.tsx
    index.ts

Или плоско:

components/
  Button.tsx
  Button.module.css
  Card.tsx
  Card.module.css

Выбор зависит от размера компонента. Если у него много файлов — папка. Если один — плоско.

Хуки

Именование: useSomething.ts.

// hooks/useLocalStorage.ts
export function useLocalStorage<T>(key: string, initial: T) {
    const [value, setValue] = useState<T>(() => {
        const stored = localStorage.getItem(key);
        return stored ? JSON.parse(stored) : initial;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);

    return [value, setValue] as const;
}

API-слой

Все запросы к серверу — в одном месте:

// api/posts.ts
const API_URL = import.meta.env.VITE_API_URL;

export async function getPosts(): Promise<Post[]> {
    const res = await fetch(`${API_URL}/posts`);
    if (!res.ok) throw new Error('Failed to fetch');
    return res.json();
}

export async function createPost(data: PostInput): Promise<Post> {
    const res = await fetch(`${API_URL}/posts`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
    });
    if (!res.ok) throw new Error('Failed to create');
    return res.json();
}

Типы

Общие типы — в types/:

export type User = {
    id: number;
    name: string;
    email: string;
};

export type Post = {
    id: number;
    title: string;
    body: string;
    author: User;
};

Локальные типы компонента — рядом с компонентом.

Утилиты

// utils/format.ts
export function formatDate(date: Date): string {
    return new Intl.DateTimeFormat('ru-RU').format(date);
}

Алиасы путей

Чтобы не писать ../../components/Button, настрой алиасы в tsconfig.json:

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"]
        }
    }
}

И в vite.config.ts:

import path from 'path';

export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
        },
    },
});

Теперь импорт:

import { Button } from '@/components/Button';

Правила

  • Не держи всё в одной папке
  • Держи связанные файлы рядом
  • Не создавай глубокую вложенность без причины
  • Переиспользуй компоненты, а не копируй
  • Логику выноси в хуки

Итоги

  • Структура зависит от размера проекта
  • Для маленьких — по типам, для больших — по фичам
  • Компоненты, хуки, API, типы, утилиты
  • Алиасы путей упрощают импорты
  • Связанные файлы держи рядом