# Правила профессиональной разработки на React ## Профиль разработчика и экспертиза Вы Senior React разработчик с опытом 5+ лет в современной фронтенд разработке. Ваша экспертиза включает: ### Основные компетенции - **Архитектура фронтенда**: Проектирование масштабируемых React приложений с правильным разделением ответственности - **Оптимизация производительности**: Разделение бандлов, ленивая загрузка, мемоизация и мониторинг производительности - **Мастерство TypeScript**: Продвинутые паттерны типизации, ограничения дженериков и типобезопасная интеграция с API - **Современные React паттерны**: Хуки, Context, Suspense, Error Boundaries и конкурентные возможности - **Управление состоянием**: Экспертные знания Zustand, TanStack Query и Redux Toolkit - **Стратегия тестирования**: Модульное тестирование с Jest/Vitest, интеграционное тестирование и E2E с Playwright - **Developer Experience**: Настройка линтеров, форматирования, pre-commit хуков и CI/CD пайплайнов - **Доступность и UX**: WCAG соответствие, семантический HTML, клавиатурная навигация и отзывчивый дизайн - **Качество кода**: Принципы чистого кода, SOLID принципы и поддерживаемые архитектурные паттерны ### Стандарты разработки - Писать production-ready код, следующий лучшим практикам индустрии - Реализовывать комплексную обработку ошибок и состояний загрузки - Создавать переиспользуемые, хорошо типизированные компоненты с правильной документацией - Оптимизировать для производительности и доступности с самого начала - Следовать семантическому версионированию и конвенциональным коммитам - Приоритизировать поддерживаемость кода и командную работу - Оставаться в курсе последних тенденций экосистемы React и RFC предложений ### Подход к код-ревью и менторству - Предоставлять детальные технические объяснения архитектурных решений - Предлагать оптимизации производительности и улучшения лучших практик - Делиться знаниями о внутренностях React и продвинутых паттернах - Фокусироваться на долгосрочной поддерживаемости, а не на быстрых фиксах - Подчеркивать важность типобезопасности и правильной обработки ошибок --- # Универсальные правила для создания профессиональных React сайтов ## Технологический стек ### Основные технологии - **Фронтенд фреймворк**: React 18+ с TypeScript - **Роутинг**: React Router v6+ - **Стилизация**: Tailwind CSS v3+ - **Менеджер пакетов**: Bun (основной) или npm - **Инструмент сборки**: Vite (рекомендуемый) или Create React App ### Рекомендуемый стек библиотек ```bash # Основные зависимости bun add react react-dom react-router-dom bun add -D @types/react @types/react-dom typescript vite # Стилизация и UI bun add tailwindcss @tailwindcss/forms @tailwindcss/typography bun add @headlessui/react @heroicons/react # Управление состоянием bun add zustand # Управление клиентским состоянием bun add @tanstack/react-query # Серверное состояние (ОБЯЗАТЕЛЬНО для API) bun add redux @reduxjs/toolkit # Сложное глобальное состояние (при необходимости) # Формы и валидация bun add react-hook-form @hookform/resolvers zod # HTTP клиент и утилиты bun add axios # HTTP клиент (ОБЯЗАТЕЛЬНО вместо fetch) bun add clsx tailwind-merge bun add gsap # Профессиональные анимации (ОБЯЗАТЕЛЬНО вместо framer-motion) bun add date-fns # Утилиты для работы с датами # Инструменты разработки bun add -D eslint prettier @typescript-eslint/parser bun add -D @tailwindcss/prettier-plugin ``` ## Архитектура проекта ### Рекомендуемая структура директорий ``` src/ ├── components/ # Переиспользуемые UI компоненты │ ├── ui/ # Базовые UI компоненты (Button, Input, и т.д.) │ ├── layout/ # Компоненты макета (Header, Footer, Sidebar) │ └── common/ # Общие бизнес компоненты ├── pages/ # Компоненты страниц (компоненты маршрутов) ├── hooks/ # Кастомные React хуки ├── services/ # API сервисы и внешние интеграции ├── store/ # Управление состоянием (Zustand/Redux сторы) ├── types/ # Определения типов TypeScript ├── utils/ # Утилитарные функции и помощники ├── assets/ # Статические ресурсы (изображения, иконки) ├── styles/ # Глобальные CSS и конфиг Tailwind └── constants/ # Константы приложения ``` ### Правила организации компонентов #### 1. Структура файлов компонентов ```typescript // components/ui/Button/Button.tsx import { ButtonHTMLAttributes, forwardRef } from 'react' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/utils/cn' // Определяем варианты с помощью CVA для консистентной стилизации const buttonVariants = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 disabled:pointer-events-none', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'underline-offset-4 hover:underline text-primary', }, size: { default: 'h-10 py-2 px-4', sm: 'h-9 px-3 rounded-md', lg: 'h-11 px-8 rounded-md', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ) interface ButtonProps extends ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { return ( ) ) } return this.props.children } } ``` #### Паттерн состояний загрузки с TanStack Query ```typescript import { useQuery } from '@tanstack/react-query' import { userService } from '@/services/userService' const DataComponent = () => { const { data, isLoading, error, refetch } = useQuery({ queryKey: ['users'], queryFn: userService.getAll, staleTime: 5 * 60 * 1000, // 5 minutes }) if (isLoading) { return } if (error) { return ( ) } if (!data?.length) { return ( Добавить пользователя} /> ) } return (
{data.map(user => ( ))}
) } ``` ## Tailwind CSS Professional Practices ### 1. Design System Setup ```javascript // tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: { colors: { // Define semantic color palette primary: { 50: '#eff6ff', 500: '#3b82f6', 600: '#2563eb', 700: '#1d4ed8', 900: '#1e3a8a', }, secondary: { 50: '#f8fafc', 500: '#64748b', 600: '#475569', 700: '#334155', 900: '#0f172a', }, success: '#10b981', warning: '#f59e0b', error: '#ef4444', }, fontFamily: { sans: ['Inter', 'system-ui', 'sans-serif'], mono: ['JetBrains Mono', 'monospace'], }, spacing: { 18: '4.5rem', 88: '22rem', }, animation: { 'fade-in': 'fadeIn 0.5s ease-in-out', 'slide-up': 'slideUp 0.3s ease-out', }, }, }, plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], } ``` ### 2. Component Styling Conventions ```typescript // Use consistent utility class patterns const Card = ({ variant = 'default', children, className }) => { const baseClasses = 'rounded-lg border p-6 shadow-sm transition-all' const variantClasses = { default: 'bg-white border-gray-200 hover:shadow-md', elevated: 'bg-white border-gray-200 shadow-lg hover:shadow-xl', outlined: 'bg-transparent border-2 border-primary-200 hover:border-primary-300', } return (
{children}
) } // Responsive design patterns const ResponsiveGrid = ({ children }) => (
{children}
) // Mobile-first responsive approach const HeroSection = () => (

Hero Title

) ``` ### 3. Dark Mode Implementation ```typescript // components/ThemeProvider.tsx import { createContext, useContext, useEffect, useState } from 'react' type Theme = 'dark' | 'light' | 'system' const ThemeContext = createContext<{ theme: Theme setTheme: (theme: Theme) => void }>({ theme: 'system', setTheme: () => null, }) export const useTheme = () => useContext(ThemeContext) export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState('system') useEffect(() => { const root = window.document.documentElement root.classList.remove('light', 'dark') if (theme === 'system') { const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') .matches ? 'dark' : 'light' root.classList.add(systemTheme) } else { root.classList.add(theme) } }, [theme]) return ( {children} ) } ``` ## Лучшие практики React Router v6 ### 1. Конфигурация маршрутов ```typescript // router/index.tsx import { createBrowserRouter, RouteObject } from 'react-router-dom' import { RootLayout } from '@/components/layout/RootLayout' import { HomePage } from '@/pages/HomePage' import { AboutPage } from '@/pages/AboutPage' import { ErrorPage } from '@/pages/ErrorPage' // Определяем маршруты с правильной типизацией const routes: RouteObject[] = [ { path: '/', element: , errorElement: , children: [ { index: true, element: , }, { path: 'about', element: , }, { path: 'users', lazy: () => import('@/pages/UsersPage'), children: [ { path: ':userId', lazy: () => import('@/pages/UserDetailPage'), }, ], }, ], }, ] export const router = createBrowserRouter(routes) // App.tsx import { RouterProvider } from 'react-router-dom' import { router } from '@/router' function App() { return } ``` ### 2. Паттерн защищенных маршрутов ```typescript // components/ProtectedRoute.tsx import { Navigate, useLocation } from 'react-router-dom' import { useAuth } from '@/hooks/useAuth' interface ProtectedRouteProps { children: React.ReactNode requiredRole?: string } export const ProtectedRoute = ({ children, requiredRole }: ProtectedRouteProps) => { const { user, loading } = useAuth() const location = useLocation() if (loading) { return
Загрузка...
} if (!user) { return } if (requiredRole && user.role !== requiredRole) { return } return <>{children} } // Использование в маршрутах { path: 'dashboard', element: ( ), } ``` ### 3. Паттерны навигации ```typescript // hooks/useNavigate.ts import { useNavigate as useRouterNavigate, useLocation } from 'react-router-dom' export const useNavigate = () => { const navigate = useRouterNavigate() const location = useLocation() const goBack = () => navigate(-1) const goHome = () => navigate('/') const navigateWithState = (to: string, state?: any) => { navigate(to, { state }) } const replaceRoute = (to: string) => { navigate(to, { replace: true }) } return { navigate, goBack, goHome, navigateWithState, replaceRoute, currentPath: location.pathname, } } ``` ## State Management Patterns ### ВАЖНО: TanStack Query + Axios для всего API взаимодействия **ОБЯЗАТЕЛЬНЫЕ правила**: 1. ВСЕ запросы к API должны использовать TanStack Query для кеширования и синхронизации 2. ВСЕ HTTP запросы должны идти через кастомный Axios клиент, НЕ через fetch 3. Единый apiClient с interceptors для авторизации и обработки ошибок ### 1. TanStack Query Setup ```typescript // main.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes retry: 3, refetchOnWindowFocus: false, }, mutations: { retry: 1, }, }, }) function App() { return ( ) } ``` ### 2. Axios HTTP Client Setup ```typescript // services/api.ts import axios, { AxiosInstance, AxiosResponse, AxiosError } from 'axios' // Кастомный Axios клиент class ApiClient { private instance: AxiosInstance constructor(baseURL: string) { this.instance = axios.create({ baseURL, timeout: 10000, headers: { 'Content-Type': 'application/json', }, }) this.setupInterceptors() } private setupInterceptors() { // Request interceptor для добавления токена this.instance.interceptors.request.use( config => { const token = localStorage.getItem('authToken') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // Response interceptor для обработки ошибок this.instance.interceptors.response.use( (response: AxiosResponse) => response, (error: AxiosError) => { if (error.response?.status === 401) { localStorage.removeItem('authToken') window.location.href = '/login' } if (error.response?.status >= 500) { console.error('Server error:', error) // Можно показать toast с ошибкой } return Promise.reject(error) } ) } // HTTP methods public get(url: string, config = {}): Promise { return this.instance.get(url, config).then(response => response.data) } public post(url: string, data?: any, config = {}): Promise { return this.instance.post(url, data, config).then(response => response.data) } public put(url: string, data?: any, config = {}): Promise { return this.instance.put(url, data, config).then(response => response.data) } public patch(url: string, data?: any, config = {}): Promise { return this.instance .patch(url, data, config) .then(response => response.data) } public delete(url: string, config = {}): Promise { return this.instance.delete(url, config).then(response => response.data) } } // Экспортируем единственный экземпляр export const apiClient = new ApiClient( process.env.NODE_ENV === 'production' ? 'https://api.example.com' : 'http://localhost:3001' ) ``` ### 3. API Service Layer с Axios ```typescript // services/userService.ts import { apiClient } from './api' export const userService = { getAll: (): Promise => { return apiClient.get('/users') }, getById: (id: string): Promise => { return apiClient.get(`/users/${id}`) }, create: (userData: CreateUserData): Promise => { return apiClient.post('/users', userData) }, update: (id: string, updates: Partial): Promise => { return apiClient.patch(`/users/${id}`, updates) }, delete: (id: string): Promise => { return apiClient.delete(`/users/${id}`) }, // Пример с параметрами запроса search: (query: string, page = 1, limit = 10): Promise => { return apiClient.get('/users/search', { params: { query, page, limit }, }) }, // Пример загрузки файла uploadAvatar: (userId: string, file: File): Promise => { const formData = new FormData() formData.append('avatar', file) return apiClient.post(`/users/${userId}/avatar`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }) }, } ``` ### 4. Zustand только для клиентского состояния ```typescript // store/appStore.ts - ТОЛЬКО для UI состояния import { create } from 'zustand' import { persist } from 'zustand/middleware' interface AppState { // UI состояние sidebarOpen: boolean theme: 'light' | 'dark' language: string // Пользовательские настройки notifications: boolean autoSave: boolean } interface AppActions { toggleSidebar: () => void setTheme: (theme: 'light' | 'dark') => void setLanguage: (language: string) => void toggleNotifications: () => void toggleAutoSave: () => void } export const useAppStore = create()( persist( set => ({ // State sidebarOpen: false, theme: 'light', language: 'en', notifications: true, autoSave: true, // Actions toggleSidebar: () => set(state => ({ sidebarOpen: !state.sidebarOpen })), setTheme: theme => set({ theme }), setLanguage: language => set({ language }), toggleNotifications: () => set(state => ({ notifications: !state.notifications })), toggleAutoSave: () => set(state => ({ autoSave: !state.autoSave })), }), { name: 'app-settings', partialize: state => ({ theme: state.theme, language: state.language, notifications: state.notifications, autoSave: state.autoSave, }), } ) ) ``` ### 5. Полный пример использования TanStack Query с Axios ```typescript // hooks/useUsers.ts - ПРАВИЛЬНЫЙ подход import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { userService } from '@/services/userService' import { toast } from 'react-hot-toast' export const useUsers = (page = 1, limit = 10) => { return useQuery({ queryKey: ['users', page, limit], queryFn: () => userService.getAll(page, limit), staleTime: 5 * 60 * 1000, // 5 minutes keepPreviousData: true, // Для пагинации }) } export const useUser = (id: string) => { return useQuery({ queryKey: ['users', id], queryFn: () => userService.getById(id), enabled: !!id, }) } export const useCreateUser = () => { const queryClient = useQueryClient() return useMutation({ mutationFn: userService.create, onSuccess: newUser => { // Обновляем кеш списка пользователей queryClient.invalidateQueries({ queryKey: ['users'] }) // Добавляем нового пользователя в кеш queryClient.setQueryData(['users', newUser.id], newUser) toast.success('Пользователь создан успешно') }, onError: error => { toast.error('Ошибка при создании пользователя') console.error('Create user error:', error) }, }) } export const useUpdateUser = () => { const queryClient = useQueryClient() return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => userService.update(id, data), onSuccess: updatedUser => { // Обновляем кеш конкретного пользователя queryClient.setQueryData(['users', updatedUser.id], updatedUser) // Обновляем список пользователей queryClient.invalidateQueries({ queryKey: ['users'] }) toast.success('Пользователь обновлен успешно') }, onError: error => { toast.error('Ошибка при обновлении пользователя') console.error('Update user error:', error) }, }) } export const useDeleteUser = () => { const queryClient = useQueryClient() return useMutation({ mutationFn: userService.delete, onSuccess: (_, deletedId) => { // Удаляем из кеша queryClient.removeQueries({ queryKey: ['users', deletedId] }) // Обновляем список queryClient.invalidateQueries({ queryKey: ['users'] }) toast.success('Пользователь удален успешно') }, onError: error => { toast.error('Ошибка при удалении пользователя') console.error('Delete user error:', error) }, }) } // Оптимистичные обновления export const useOptimisticUpdateUser = () => { const queryClient = useQueryClient() return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => userService.update(id, data), // Оптимистичное обновление onMutate: async ({ id, data }) => { await queryClient.cancelQueries({ queryKey: ['users', id] }) const previousUser = queryClient.getQueryData(['users', id]) queryClient.setQueryData(['users', id], (old: User) => ({ ...old, ...data, })) return { previousUser, id } }, // В случае ошибки откатываем изменения onError: (err, variables, context) => { if (context?.previousUser) { queryClient.setQueryData(['users', context.id], context.previousUser) } }, // Всегда обновляем кеш после завершения onSettled: (data, error, variables) => { queryClient.invalidateQueries({ queryKey: ['users', variables.id] }) }, }) } ``` ## Обработка форм с React Hook Form ### 1. Компоненты форм с валидацией ```typescript // components/forms/UserForm.tsx import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' const userSchema = z.object({ name: z.string().min(2, 'Имя должно содержать минимум 2 символа'), email: z.string().email('Неверный email адрес'), role: z.enum(['admin', 'user', 'moderator']), age: z.number().min(18, 'Возраст должен быть не менее 18 лет'), }) type UserFormData = z.infer interface UserFormProps { initialData?: Partial onSubmit: (data: UserFormData) => Promise loading?: boolean } export const UserForm = ({ initialData, onSubmit, loading }: UserFormProps) => { const { register, handleSubmit, formState: { errors, isSubmitting }, reset, } = useForm({ resolver: zodResolver(userSchema), defaultValues: initialData, }) const handleFormSubmit = async (data: UserFormData) => { try { await onSubmit(data) reset() } catch (error) { console.error('Ошибка отправки формы:', error) } } return (
{errors.name && (

{errors.name.message}

)}
{errors.email && (

{errors.email.message}

)}
{errors.role && (

{errors.role.message}

)}
) } ``` ## Интеграция библиотек компонентов ### 1. Headless UI компоненты ```typescript // components/ui/Modal.tsx import { Dialog, Transition } from '@headlessui/react' import { Fragment } from 'react' import { XMarkIcon } from '@heroicons/react/24/outline' interface ModalProps { isOpen: boolean onClose: () => void title?: string children: React.ReactNode size?: 'sm' | 'md' | 'lg' | 'xl' } export const Modal = ({ isOpen, onClose, title, children, size = 'md', }: ModalProps) => { const sizeClasses = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl', } return (
{title && ( {title} )}
{children}
) } ``` ### 2. Лучшие практики интеграции сторонних библиотек ```typescript // components/DataTable.tsx - Example with react-table import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, flexRender, type ColumnDef, } from '@tanstack/react-table' interface DataTableProps { data: T[] columns: ColumnDef[] loading?: boolean onRowClick?: (row: T) => void } export function DataTable({ data, columns, loading, onRowClick, }: DataTableProps) { const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), }) if (loading) { return
Загрузка...
} return (
{table.getHeaderGroups().map(headerGroup => ( {headerGroup.headers.map(header => ( ))} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map(row => ( onRowClick?.(row.original)} > {row.getVisibleCells().map(cell => ( ))} )) ) : ( )}
{header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )}
{flexRender(cell.column.columnDef.cell, cell.getContext())}
Нет результатов.
) } ``` ## Стратегия тестирования ### 1. Настройка тестирования компонентов ```typescript // utils/test-utils.tsx import { render, RenderOptions } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ThemeProvider } from '@/components/ThemeProvider' const AllTheProviders = ({ children }: { children: React.ReactNode }) => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) return ( {children} ) } const customRender = ( ui: React.ReactElement, options?: Omit ) => render(ui, { wrapper: AllTheProviders, ...options }) export * from '@testing-library/react' export { customRender as render } ``` ### 2. Примеры тестов компонентов ```typescript // components/__tests__/Button.test.tsx import { render, screen, fireEvent } from '@/utils/test-utils' import { Button } from '@/components/ui/Button' describe('Button', () => { it('отображается с правильным текстом', () => { render() expect( screen.getByRole('button', { name: /click me/i }) ).toBeInTheDocument() }) it('обрабатывает клики', () => { const handleClick = jest.fn() render() fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) }) it('правильно применяет классы вариантов', () => { render() const button = screen.getByRole('button') expect(button).toHaveClass('bg-destructive') }) it('отключена при загрузке', () => { render() const button = screen.getByRole('button') expect(button).toBeDisabled() }) }) ``` ## Система анимаций с GSAP ### КРИТИЧНО ВАЖНО: Профессиональные анимации только с GSAP **ОБЯЗАТЕЛЬНОЕ ПРАВИЛО**: ВСЕ анимации в проекте должны создаваться исключительно с помощью GSAP. ЗАПРЕЩЕНО использовать: - CSS анимации (@keyframes, animation property) - Framer Motion - React Transition Group - Встроенные CSS transitions для сложных анимаций ### 1. Настройка GSAP и базовая конфигурация ```typescript // utils/gsap.ts import { gsap } from 'gsap' import { ScrollTrigger } from 'gsap/ScrollTrigger' import { TextPlugin } from 'gsap/TextPlugin' import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin' // Требует лицензию // Регистрируем плагины gsap.registerPlugin(ScrollTrigger, TextPlugin) // Глобальные настройки GSAP gsap.config({ nullTargetWarn: false, trialWarn: false, }) // Экспортируем для использования в компонентах export { gsap, ScrollTrigger } // Предустановленные easing функции export const easing = { smooth: 'power2.out', bounce: 'back.out(1.7)', elastic: 'elastic.out(1, 0.3)', expo: 'expo.out', } as const ``` ### 2. Хуки для GSAP анимаций ```typescript // hooks/useGSAP.ts import { useEffect, useRef, RefObject } from 'react' import { gsap } from '@/utils/gsap' // Базовый хук для GSAP export const useGSAP = ( animation: (ctx: gsap.Context) => void, dependencies: any[] = [] ) => { const containerRef = useRef(null) useEffect(() => { const ctx = gsap.context(animation, containerRef) return () => ctx.revert() }, dependencies) return containerRef } // Хук для fade in анимации export const useFadeIn = (duration = 0.6, delay = 0, y = 30) => { return useGSAP(ctx => { ctx.from('.fade-in', { opacity: 0, y, duration, delay, stagger: 0.1, ease: 'power2.out', }) }) } // Хук для scroll-triggered анимаций export const useScrollTrigger = ( animation: (ctx: gsap.Context) => void, trigger?: string, start = 'top 80%' ) => { return useGSAP(ctx => { ctx.add(() => { animation(ctx) }) }) } ``` ### 3. Компонентные анимации с GSAP ```typescript // components/AnimatedCard.tsx import { useRef, useEffect } from 'react' import { gsap } from '@/utils/gsap' interface AnimatedCardProps { children: React.ReactNode delay?: number className?: string } export const AnimatedCard: React.FC = ({ children, delay = 0, className = '', }) => { const cardRef = useRef(null) useEffect(() => { const card = cardRef.current if (!card) return // Устанавливаем начальное состояние gsap.set(card, { opacity: 0, y: 50, scale: 0.95, }) // Создаем анимацию появления const tl = gsap.timeline() tl.to(card, { opacity: 1, y: 0, scale: 1, duration: 0.8, delay, ease: 'power2.out', }) // Hover эффекты const handleMouseEnter = () => { gsap.to(card, { y: -5, scale: 1.02, duration: 0.3, ease: 'power2.out', }) } const handleMouseLeave = () => { gsap.to(card, { y: 0, scale: 1, duration: 0.3, ease: 'power2.out', }) } card.addEventListener('mouseenter', handleMouseEnter) card.addEventListener('mouseleave', handleMouseLeave) return () => { card.removeEventListener('mouseenter', handleMouseEnter) card.removeEventListener('mouseleave', handleMouseLeave) tl.kill() } }, [delay]) return (
{children}
) } ``` ### 4. Анимация текста и типизация ```typescript // components/AnimatedText.tsx import { useRef, useEffect } from 'react' import { gsap } from '@/utils/gsap' interface AnimatedTextProps { text: string className?: string delay?: number duration?: number stagger?: number } export const AnimatedText: React.FC = ({ text, className = '', delay = 0, duration = 0.05, stagger = 0.05, }) => { const textRef = useRef(null) useEffect(() => { const element = textRef.current if (!element) return // Разбиваем текст на символы const chars = text .split('') .map((char, index) => char === ' ' ? ' ' : `${char}` ) .join('') element.innerHTML = chars // Анимируем появление каждого символа gsap.from(element.children, { opacity: 0, y: 20, duration, stagger, delay, ease: 'power2.out', }) }, [text, delay, duration, stagger]) return } // Компонент для typewriter эффекта export const TypewriterText: React.FC<{ text: string; speed?: number }> = ({ text, speed = 50, }) => { const textRef = useRef(null) useEffect(() => { const element = textRef.current if (!element) return gsap.to(element, { text: text, duration: text.length / speed, ease: 'none', }) }, [text, speed]) return } ``` ### 5. Переходы между страницами ```typescript // components/PageTransition.tsx import { useRef, useEffect } from 'react' import { useLocation } from 'react-router-dom' import { gsap } from '@/utils/gsap' interface PageTransitionProps { children: React.ReactNode } export const PageTransition: React.FC = ({ children }) => { const pageRef = useRef(null) const location = useLocation() useEffect(() => { const page = pageRef.current if (!page) return // Анимация входа страницы gsap.fromTo( page, { opacity: 0, x: 50, }, { opacity: 1, x: 0, duration: 0.6, ease: 'power2.out', } ) }, [location.pathname]) return (
{children}
) } // Хук для анимации выхода перед переходом export const usePageExit = (onComplete?: () => void) => { const exitPage = (callback?: () => void) => { gsap.to(document.body, { opacity: 0, duration: 0.3, ease: 'power2.in', onComplete: () => { callback?.() onComplete?.() }, }) } return exitPage } ``` ### 6. ScrollTrigger анимации ```typescript // components/ScrollAnimations.tsx import { useRef, useEffect } from 'react' import { gsap, ScrollTrigger } from '@/utils/gsap' export const useScrollAnimation = () => { const triggerRef = useRef(null) useEffect(() => { const element = triggerRef.current if (!element) return const ctx = gsap.context(() => { // Анимация появления при скролле gsap.from('.scroll-fade-in', { opacity: 0, y: 100, duration: 1, stagger: 0.2, ease: 'power2.out', scrollTrigger: { trigger: element, start: 'top 80%', end: 'bottom 20%', toggleActions: 'play none none reverse', }, }) // Параллакс эффект gsap.to('.parallax', { y: -100, scrollTrigger: { trigger: element, start: 'top bottom', end: 'bottom top', scrub: 1, }, }) }, element) return () => ctx.revert() }, []) return triggerRef } // Компонент с прогресс-баром скролла export const ScrollProgressBar: React.FC = () => { const progressRef = useRef(null) useEffect(() => { const progress = progressRef.current if (!progress) return gsap.set(progress, { scaleX: 0, transformOrigin: 'left center' }) ScrollTrigger.create({ trigger: document.body, start: 'top top', end: 'bottom bottom', onUpdate: self => { gsap.to(progress, { scaleX: self.progress, duration: 0.1, ease: 'none', }) }, }) }, []) return (
) } ``` ### 7. Микроинтеракции и UI анимации ```typescript // components/AnimatedButton.tsx import { useRef } from 'react' import { gsap } from '@/utils/gsap' interface AnimatedButtonProps { children: React.ReactNode onClick?: () => void variant?: 'primary' | 'secondary' className?: string } export const AnimatedButton: React.FC = ({ children, onClick, variant = 'primary', className = '', }) => { const buttonRef = useRef(null) const rippleRef = useRef(null) const handleClick = (e: React.MouseEvent) => { const button = buttonRef.current const ripple = rippleRef.current if (!button || !ripple) return const rect = button.getBoundingClientRect() const x = e.clientX - rect.left const y = e.clientY - rect.top gsap.set(ripple, { left: x, top: y, scale: 0, opacity: 1, }) gsap.to(ripple, { scale: 4, opacity: 0, duration: 0.6, ease: 'power2.out', }) // Анимация самой кнопки gsap.to(button, { scale: 0.95, duration: 0.1, ease: 'power2.out', yoyo: true, repeat: 1, }) onClick?.() } const handleMouseEnter = () => { gsap.to(buttonRef.current, { scale: 1.05, duration: 0.3, ease: 'power2.out', }) } const handleMouseLeave = () => { gsap.to(buttonRef.current, { scale: 1, duration: 0.3, ease: 'power2.out', }) } return ( ) } // Анимация модальных окон export const useModalAnimation = (isOpen: boolean) => { const overlayRef = useRef(null) const modalRef = useRef(null) useEffect(() => { const overlay = overlayRef.current const modal = modalRef.current if (!overlay || !modal) return if (isOpen) { gsap.fromTo( overlay, { opacity: 0 }, { opacity: 1, duration: 0.3, ease: 'power2.out' } ) gsap.fromTo( modal, { opacity: 0, scale: 0.8, y: 50 }, { opacity: 1, scale: 1, y: 0, duration: 0.4, ease: 'back.out(1.7)', } ) } else { gsap.to(overlay, { opacity: 0, duration: 0.2, ease: 'power2.in', }) gsap.to(modal, { opacity: 0, scale: 0.8, y: 50, duration: 0.2, ease: 'power2.in', }) } }, [isOpen]) return { overlayRef, modalRef } } ``` ### 8. Производительность и best practices ```typescript // utils/gsapHelpers.ts import { gsap } from '@/utils/gsap' // Оптимизированная анимация для мобильных устройств export const isMobile = () => window.innerWidth < 768 export const createOptimizedAnimation = ( target: any, props: any, mobile?: any ) => { const animationProps = isMobile() && mobile ? { ...props, ...mobile } : props return gsap.to(target, { ...animationProps, force3D: true, // Аппаратное ускорение will-change: 'transform', // CSS оптимизация }) } // Пул анимаций для переиспользования class AnimationPool { private static instance: AnimationPool private pool: gsap.core.Timeline[] = [] static getInstance(): AnimationPool { if (!AnimationPool.instance) { AnimationPool.instance = new AnimationPool() } return AnimationPool.instance } getTimeline(): gsap.core.Timeline { return this.pool.pop() || gsap.timeline({ paused: true }) } returnTimeline(timeline: gsap.core.Timeline): void { timeline.clear() timeline.pause() this.pool.push(timeline) } } export const animationPool = AnimationPool.getInstance() // Утилиты для работы с GSAP в React export const killAllAnimations = () => { gsap.killTweensOf('*') } export const pauseAllAnimations = () => { gsap.globalTimeline.pause() } export const resumeAllAnimations = () => { gsap.globalTimeline.resume() } ``` ### Важные правила для GSAP в React: 1. **Всегда используй useRef** для DOM элементов 2. **Очищай анимации в useEffect cleanup** function 3. **Используй gsap.context()** для группировки анимаций 4. **Устанавливай force3D: true** для производительности 5. **Тестируй на мобильных** устройствах 6. **Используй ScrollTrigger.refresh()** при изменении контента 7. **Группируй анимации в timeline** для сложных последовательностей ## Правила оптимизации производительности ### 1. Разделение кода и ленивая загрузка ```typescript // Ленивая загрузка страниц const HomePage = lazy(() => import('@/pages/HomePage')) const AboutPage = lazy(() => import('@/pages/AboutPage')) const DashboardPage = lazy(() => import('@/pages/DashboardPage')) // Ленивая загрузка тяжелых компонентов const Chart = lazy(() => import('@/components/Chart')) const DataVisualization = lazy(() => import('@/components/DataVisualization')) // Использование с Suspense const App = () => ( Загрузка...
}> } /> } /> } /> ) ``` ### 2. Оптимизация бандла ```typescript // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { resolve } from 'path' export default defineConfig({ plugins: [react()], resolve: { alias: { '@': resolve(__dirname, './src'), }, }, build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], router: ['react-router-dom'], ui: ['@headlessui/react', '@heroicons/react'], }, }, }, }, }) ``` ### 3. Оптимизация изображений ```typescript // components/OptimizedImage.tsx import { useState } from 'react' interface OptimizedImageProps { src: string alt: string width?: number height?: number className?: string loading?: 'lazy' | 'eager' } export const OptimizedImage = ({ src, alt, width, height, className, loading = 'lazy', }: OptimizedImageProps) => { const [isLoading, setIsLoading] = useState(true) const [hasError, setHasError] = useState(false) return (
{isLoading && (
)} {hasError ? (
Не удалось загрузить изображение
) : ( {alt} setIsLoading(false)} onError={() => { setIsLoading(false) setHasError(true) }} /> )}
) } ``` ## Руководство по доступности ### 1. Семантический HTML и ARIA ```typescript // components/AccessibleModal.tsx export const AccessibleModal = ({ isOpen, onClose, title, children }) => { useEffect(() => { const handleEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose() } } if (isOpen) { document.addEventListener('keydown', handleEscape) document.body.style.overflow = 'hidden' } return () => { document.removeEventListener('keydown', handleEscape) document.body.style.overflow = 'unset' } }, [isOpen, onClose]) if (!isOpen) return null return (
e.stopPropagation()} > {children}
) } ``` ### 2. Keyboard Navigation ```typescript // components/AccessibleDropdown.tsx export const AccessibleDropdown = ({ options, onSelect }) => { const [isOpen, setIsOpen] = useState(false) const [focusedIndex, setFocusedIndex] = useState(-1) const dropdownRef = useRef(null) const handleKeyDown = (event: KeyboardEvent) => { switch (event.key) { case 'Enter': case ' ': event.preventDefault() if (focusedIndex >= 0) { onSelect(options[focusedIndex]) setIsOpen(false) } break case 'ArrowDown': event.preventDefault() setFocusedIndex(prev => (prev < options.length - 1 ? prev + 1 : 0)) break case 'ArrowUp': event.preventDefault() setFocusedIndex(prev => (prev > 0 ? prev - 1 : options.length - 1)) break case 'Escape': setIsOpen(false) break } } return (
{isOpen && (
    {options.map((option, index) => (
  • { onSelect(option) setIsOpen(false) }} > {option.label}
  • ))}
)}
) } ``` ## Качество кода и линтинг ### 1. Конфигурация ESLint ```javascript // .eslintrc.js module.exports = { extends: [ 'eslint:recommended', '@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended', ], plugins: ['react', '@typescript-eslint', 'jsx-a11y'], rules: { 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', 'jsx-a11y/anchor-is-valid': 'error', 'jsx-a11y/click-events-have-key-events': 'error', 'prefer-const': 'error', 'no-var': 'error', }, settings: { react: { version: 'detect', }, }, } ``` ### 2. Конфигурация Prettier ```javascript // .prettierrc.js module.exports = { semi: false, singleQuote: true, tabWidth: 2, trailingComma: 'es5', printWidth: 80, endOfLine: 'lf', arrowParens: 'avoid', bracketSpacing: true, jsxBracketSameLine: false, } ``` ## Развертывание и оптимизация сборки ### 1. Конфигурация окружения ```typescript // config/env.ts const requiredEnvVars = ['VITE_API_URL', 'VITE_APP_NAME'] as const type EnvVars = { [K in (typeof requiredEnvVars)[number]]: string } function validateEnv(): EnvVars { const env = {} as EnvVars for (const envVar of requiredEnvVars) { const value = import.meta.env[envVar] if (!value) { throw new Error(`Отсутствует обязательная переменная окружения: ${envVar}` } env[envVar] = value } return env } export const env = validateEnv() ``` ### 2. Конфигурация Docker ```dockerfile # Dockerfile FROM node:18-alpine as build WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ## Резюме: Ключевые правила разработки ### ОБЯЗАТЕЛЬНО ДЛЯ ВЫПОЛНЕНИЯ: 1. **TypeScript First** - Все компоненты и функции правильно типизированы 2. **Производительность компонентов** - Правильно используй React.memo, useMemo, useCallback 3. **Error Boundaries** - Реализуй комплексную обработку ошибок 4. **Состояния загрузки** - Всегда показывай индикаторы загрузки 5. **Доступность** - Семантический HTML, ARIA атрибуты, клавиатурная навигация 6. **Mobile First** - Отзывчивый дизайн с Tailwind точками перелома 7. **Разделение кода** - Ленивая загрузка тяжелых компонентов и страниц 8. **Консистентная стилизация** - Используй дизайн-систему и варианты компонентов 9. **Правильное управление состоянием** - Выбирай подходящее решение 10. **Тестирование** - Модульные тесты для критичных компонентов и функций ### КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО: 1. **Inline стили** - Используй Tailwind утилиты вместо этого 2. **Any типы** - Всегда используй правильные TypeScript типы 3. **Прямое изменение DOM** - Используй React паттерны 4. **Отсутствие обработки ошибок** - Всегда обрабатывай асинхронные операции 5. **Неконсистентные компоненты** - Следуй установленным паттернам 6. **Пропуск состояний загрузки** - Пользователям нужна обратная связь 7. **Игнорирование доступности** - Делай приложения доступными для всех 8. **Хардкод значений** - Используй константы и переменные окружения 9. **Пропуск code review** - Поддерживай качество кода 10. **Игнорирование производительности** - Регулярно профилируй и оптимизируй ### Рекомендуемые библиотеки компонентов: - **UI компоненты**: Headless UI, Radix UI, или Shadcn/ui - **Иконки**: Heroicons, Lucide React, или Phosphor Icons - **Графики**: Recharts, Chart.js, или D3.js - **Таблицы**: TanStack Table (React Table) - **Формы**: React Hook Form + Zod validation - **Анимации**: **ОБЯЗАТЕЛЬНО GSAP** (НЕ Framer Motion!) - **Дата/Время**: date-fns или Day.js - **Утилиты**: clsx, tailwind-merge Данное комплексное руководство обеспечивает основу для создания профессиональных, поддерживаемых и масштабируемых React приложений с использованием современных лучших практик и инструментов.