От пропсов до мемоизации: как ИИ пишет React-компоненты, которые не стыдно показать код-ревью

Вы когда-нибудь просили нейросеть сгенерировать React-компонент и получали нечто, что работает, но выглядит так, будто его писал стажёр, который прочитал только половину документации? Я — да, и это нормально. Дело в том, что промты для ИИ — это тоже код. Чем точнее спецификация, тем лучше результат. За последний год я протестировал десятки промтов для генерации React-компонентов: от простых кнопок до сложных виртуализированных таблиц. В этой подборке — десять проверенных шаблонов, которые реально экономят время и не заставляют краснеть на код-ревью.

Базовые промты: фундамент, на котором всё держится

Начнём с простого. Если вы новичок в промпт-инжиниринге, эти три шаблона — ваша отправная точка. Они научат вас правильно формулировать задачу и получать предсказуемый результат.

1. Классический функциональный компонент

Задача: Создать компонент, который принимает пропсы и рендерит простую разметку.

Промт:

Создай функциональный компонент React TypeScript под названием UserCard.
Он принимает пропсы: name: string, email: string, role: 'admin' | 'user'.
Рендерит карточку с именем, email и бейджем роли.
Стилизуй с помощью CSS-модулей.

Пример результата:

import styles from './UserCard.module.css';

interface UserCardProps {
  name: string;
  email: string;
  role: 'admin' | 'user';
}

export const UserCard: React.FC<UserCardProps> = ({ name, email, role }) => {
  return (
    <div className={styles.card}>
      <h3>{name}</h3>
      <p>{email}</p>
      <span className={role === 'admin' ? styles.admin : styles.user}>{role}</span>
    </div>
  );
};

Почему это работает: вы явно указали типы пропсов, что предотвращает ошибки на этапе компиляции. Пропс role ограничен значениями, что улучшает автодополнение в IDE.

2. Компонент с хуками состояния и эффектов

Задача: Реализовать компонент с внутренним состоянием и побочными эффектами.

Промт:

Напиши компонент Timer, который показывает секундомер.
Используй useState для хранения секунд и useEffect для интервала.
Добавь кнопки Старт, Пауза, Сброс.
Таймер должен быть в TypeScript с типами для всех функций.

Пример результата:

import { useState, useEffect } from 'react';

export const Timer: React.FC = () => {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    if (!isRunning) return;
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval);
  }, [isRunning]);

  return (
    <div>
      <p>{seconds} сек</p>
      <button onClick={() => setIsRunning(true)}>Старт</button>
      <button onClick={() => setIsRunning(false)}>Пауза</button>
      <button onClick={() => { setSeconds(0); setIsRunning(false); }}>Сброс</button>
    </div>
  );
};

Здесь важно, что промт требует очистку интервала в useEffect — это предотвращает утечки памяти.

3. Контекст и провайдер

Задача: Создать глобальное состояние с помощью Context API.

Промт:

Создай ThemeContext и ThemeProvider для управления темой (светлая/тёмная).
Используй useContext в компоненте Button, чтобы менять стили в зависимости от темы.
Типизируй контекст.

Пример результата:

// ThemeContext.tsx
import { createContext, useContext, useState } from 'react';

type Theme = 'light' | 'dark';
interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = useState<Theme>('light');
  const toggleTheme = () => setTheme(t => t === 'light' ? 'dark' : 'light');
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
  return ctx;
};
// Button.tsx
import { useTheme } from './ThemeContext';
import styles from './Button.module.css';

export const Button: React.FC = ({ children }) => {
  const { theme } = useTheme();
  return <button className={styles[theme]}>{children}</button>;
};

Такой подход — стандарт в крупных приложениях, и ИИ легко его воспроизводит, если чётко описать задачу.

Продвинутые промты: когда нужно не просто «работает»

Здесь мы выходим на уровень выше: оптимизация, безопасность, тестирование. Эти промты пригодятся, когда проект уже вышел из стадии прототипа.

4. Мемоизация с React.memo

Задача: Оптимизировать компонент, чтобы избежать лишних ререндеров.

Промт:

Создай компонент ProductCard с пропсами product: Product и onAdd: (id: number) => void.
Оберни в React.memo, чтобы компонент не ререндерился, если пропсы не изменились.
Типизируй Product.

Пример результата:

import React from 'react';

interface Product {
  id: number;
  name: string;
  price: number;
}

interface ProductCardProps {
  product: Product;
  onAdd: (id: number) => void;
}

export const ProductCard: React.FC<ProductCardProps> = React.memo(({ product, onAdd }) => {
  console.log('Рендер ProductCard', product.id);
  return (
    <div>
      <h4>{product.name}</h4>
      <p>${product.price}</p>
      <button onClick={() => onAdd(product.id)}>Добавить</button>
    </div>
  );
});

Но помните: React.memo — не серебряная пуля. Если пропсы содержат функции, которые создаются заново при каждом рендере родителя, мемоизация не сработает. Поэтому обязательно используйте useCallback в родительском компоненте.

5. useCallback и useMemo

Задача: Оптимизировать вычисления и стабилизировать ссылки на функции.

Промт:

Создай компонент ShoppingList, который получает items: Item[] и onRemove: (id: number) => void.
Используй useMemo для вычисления общей стоимости и useCallback для onRemove.
Покажи пример использования.

Пример результата:

import { useMemo, useCallback } from 'react';

interface Item {
  id: number;
  name: string;
  price: number;
}

interface ShoppingListProps {
  items: Item[];
}

export const ShoppingList: React.FC<ShoppingListProps> = ({ items }) => {
  const total = useMemo(() => items.reduce((acc, item) => acc + item.price, 0), [items]);
  const handleRemove = useCallback((id: number) => {
    // логика удаления
  }, []);

  return (
    <div>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name} — ${item.price}
            <button onClick={() => handleRemove(item.id)}>Удалить</button>
          </li>
        ))}
      </ul>
      <p>Итого: ${total}</p>
    </div>
  );
};

ИИ часто генерирует похожий код, но не всегда объясняет, почему items должен быть в зависимостях useMemo. Уточните это в промте, чтобы получить полное решение.

6. Код-сплиттинг с React.lazy

Задача: Разбить приложение на части для уменьшения начальной загрузки.

Промт:

Реализуй ленивую загрузку для компонента Dashboard.
Используй React.lazy и Suspense с фолбэком "Загрузка...".
Покажи, как импортировать Dashboard в App.

Пример результата:

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard'));

export const App: React.FC = () => {
  return (
    <Suspense fallback={<div>Загрузка...</div>}>
      <Dashboard />
    </Suspense>
  );
};

Это особенно полезно для больших приложений. По данным Web.dev, уменьшение размера начального JavaScript на 100 КБ сокращает время загрузки на секунду на мобильных устройствах (источник: web.dev/articles/reduce-javascript-payloads).

Экспертные промты: когда ИИ становится архитектором

Теперь самое интересное: промты, которые заставляют нейросеть думать как старший разработчик. Здесь важны детали, которые обычно упускают из виду.

7. Виртуализация списков с react-window

Задача: Отрисовать большой список без потери производительности.

Промт:

Создай компонент VirtualList с использованием react-window.
Он должен принимать items: string[] и высоту строки.
Используй FixedSizeList.

Пример результата:

import { FixedSizeList as List } from 'react-window';

interface VirtualListProps {
  items: string[];
  itemHeight: number;
}

export const VirtualList: React.FC<VirtualListProps> = ({ items, itemHeight }) => {
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={itemHeight}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </List>
  );
};

Если вы работаете со списками из тысяч элементов, виртуализация — обязательный шаг. Библиотека react-window — стандарт в индустрии.

8. Кастомный хук для управления формами

Задача: Создать переиспользуемый хук для валидации форм.

Промт:

Напиши хук useForm, который принимает initialValues и validate функцию.
Возвращает values, errors, handleChange, handleSubmit.
Типизируй всё.

Пример результата:

import { useState } from 'react';

interface FormValues {
  [key: string]: string;
}

type Errors<T> = Partial<Record<keyof T, string>>;

export const useForm = <T extends FormValues>(
  initialValues: T,
  validate: (values: T) => Errors<T>
) => {
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Errors<T>>({});

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValues({ ...values, [e.target.name]: e.target.value });
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const validationErrors = validate(values);
    setErrors(validationErrors);
    if (Object.keys(validationErrors).length === 0) {
      // отправка данных
    }
  };

  return { values, errors, handleChange, handleSubmit };
};

Этот хук легко переиспользовать в разных формах, и ИИ отлично справляется с такой задачей, если дать чёткое ТЗ.

9. Оптимизация с useTransition

Задача: Сделать интерфейс отзывчивым при тяжёлых обновлениях.

Промт:

Создай компонент SearchResults, который использует useTransition для фильтрации списка.
Показывай индикатор загрузки во время перехода.

Пример результата:

import { useState, useTransition } from 'react';

const allItems = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);

export const SearchResults: React.FC = () => {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();

  const filtered = allItems.filter(item => item.includes(query));

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setQuery(value);
    startTransition(() => {
      // Тяжёлая фильтрация происходит здесь
      setFilteredItems(allItems.filter(item => item.includes(value)));
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <p>Загрузка...</p>}
      <ul>
        {filtered.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
};

useTransition появился в React 18 и позволяет помечать не срочные обновления состояния, сохраняя отзывчивость интерфейса.

10. Компонент с асинхронной загрузкой данных

Задача: Реализовать загрузку данных с сервера с обработкой состояний.

Промт:

Создай компонент UserProfile, который загружает данные по ID с помощью fetch.
Покажи состояния: загрузка, ошибка, успех.
Используй useEffect и AbortController для отмены запроса при размонтировании.

Пример результата:

import { useState, useEffect } from 'react';

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

export const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);
    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error('Ошибка загрузки');
        return res.json();
      })
      .then(data => setUser(data))
      .catch(err => {
        if (err.name !== 'AbortError') setError(err.message);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [userId]);

  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error}</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
};

Обратите внимание на AbortController — это важный паттерн для предотвращения гонок состояний.

Как адаптировать промты под свой проект

Главный секрет — не копировать промты вслепую, а адаптировать. Замените типы, названия и стили под свой контекст. Добавьте требования к тестам, если нужно: «Напиши юнит-тесты с Jest и Testing Library». Укажите версию React, если используете старую — например, React 17 не поддерживает useTransition.

Итоги

Эти десять промтов покрывают путь от простых функциональных компонентов до оптимизированных с мемоизацией и код-сплиттингом. Используйте их как отправную точку — и не забывайте проверять сгенерированный код. ИИ — мощный инструмент, но ответственность за качество всегда лежит на вас. Экспериментируйте, добавляйте свои требования, и вы увидите, как скорость разработки растёт, а количество багов падает.

← Все статьи

Комментарии

Читайте также

SQL-запросы как ракета: 15 промтов для отладки, оптимизации и проектирования баз данных

26 августа 2026

Как превратить ChatGPT в личного репетитора английского: 18 промтов, которые реально работают

26 августа 2026

Мультимодальный ИИ в деле: 15 промтов, которые превращают текст, картинки и видео в рабочие проекты

26 августа 2026

Легаси-код и ИИ: 10 промтов, которые превращают «болото» в чистую архитектуру

26 августа 2026

SQL-запросы и PostgreSQL: как ИИ превращает рутину в магию — 15 промтов для разработчика и аналитика

26 августа 2026

15 промтов для Excel и Google Sheets, которые превратят ваш хаос в чистую аналитику

26 августа 2026

От прототипа до продакшена за вечер: как собрать лендинг на промтах и не сойти с ума

25 августа 2026

SQL и NoSQL под контролем: 10 промтов, которые заменят DBA, если вы разработчик

25 августа 2026

PostgreSQL под капотом: как превратить LLM в персонального DBA и не спалить прод

25 августа 2026