Введение
Генерация кода с помощью больших языковых моделей (LLM) стала повседневной практикой для разработчиков. Однако, как показывает опыт, качество результата напрямую зависит от качества запроса. Плохо сформулированный промт приводит к шаблонному, неработающему или небезопасному коду. Эта подборка из 12 промтов для JavaScript и TypeScript поможет вам получать готовые решения для реальных задач: от React-компонентов до серверных утилит на Node.js. Каждый промт проверен на практике и включает пример результата с разбором.
1. Базовый промт: генерация React-компонента с TypeScript
Задача: Создать переиспользуемый компонент модального окна с пропсами.
Промт:
Напиши React-компонент Modal на TypeScript. Он должен принимать пропсы: isOpen (boolean), onClose (функция без аргументов), title (string), children (React.ReactNode). Используй портал (createPortal) для рендеринга в body. Добавь анимацию появления с CSS transition. Закрытие по клику на фон и по Escape. Не используй сторонних библиотек.
Результат:
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children }) => {
const [visible, setVisible] = useState(false);
const overlayRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen) {
requestAnimationFrame(() => setVisible(true));
} else {
setVisible(false);
}
}, [isOpen]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) onClose();
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
const handleOverlayClick = (e: React.MouseEvent) => {
if (e.target === overlayRef.current) onClose();
};
if (!isOpen) return null;
return createPortal(
<div
ref={overlayRef}
onClick={handleOverlayClick}
style={{
position: 'fixed',
inset: 0,
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: visible ? 1 : 0,
transition: 'opacity 0.3s ease',
}}
>
<div style={{ background: '#fff', padding: 24, borderRadius: 8, minWidth: 400 }}>
<h2>{title}</h2>
{children}
</div>
</div>,
document.body
);
};
export default Modal;
Разбор: Промт явно указывает требования: пропсы, портал, анимация, обработка событий. Результат — готовый компонент без зависимостей. Для продакшна стоит добавить управление фокусом и aria-атрибуты.
2. Промт для генерации утилиты: debounce с TypeScript
Задача: Написать функцию debounce с правильной типизацией.
Промт:
Напиши функцию debounce на TypeScript. Она должна принимать функцию F и задержку в миллисекундах. Возвращать новую функцию с теми же аргументами и возвращаемым типом, но с отложенным вызовом. Добавь метод cancel для отмены. Не используй сторонние библиотеки.
Результат:
type DebouncedFunction<T extends (...args: any[]) => any> = {
(...args: Parameters<T>): void;
cancel: () => void;
};
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): DebouncedFunction<T> {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const debounced = (...args: Parameters<T>) => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
fn(...args);
timeoutId = null;
}, delay);
};
debounced.cancel = () => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return debounced;
}
Разбор: Использование Parameters
3. Промт для генерации API-клиента на Node.js
Задача: Создать HTTP-клиент с повторными попытками и кэшированием.
Промт:
Напиши класс HttpClient на TypeScript для Node.js. Он должен поддерживать методы GET, POST, PUT, DELETE. Добавь логику повторных попыток при ошибках 5xx (до 3 раз с экспоненциальной задержкой). Реализуй in-memory кэш с TTL 60 секунд. Используй только встроенный модуль http/https. Верни типизированный ответ.
Результат (сокращённо):
import * as https from 'https';
import * as http from 'http';
interface CacheEntry<T> {
data: T;
expiresAt: number;
}
class HttpClient {
private cache = new Map<string, CacheEntry<any>>();
async request<T>(url: string, options?: RequestOptions): Promise<T> {
const cacheKey = `${options?.method || 'GET'}:${url}`;
const cached = this.cache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return cached.data as T;
}
let attempt = 0;
const maxRetries = 3;
while (attempt <= maxRetries) {
try {
const data = await this.makeRequest<T>(url, options);
if (options?.method === 'GET') {
this.cache.set(cacheKey, { data, expiresAt: Date.now() + 60_000 });
}
return data;
} catch (err) {
if (attempt < maxRetries && isServerError(err)) {
await this.delay(Math.pow(2, attempt) * 1000);
attempt++;
} else {
throw err;
}
}
}
throw new Error('Request failed');
}
private makeRequest<T>(url: string, options?: RequestOptions): Promise<T> {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
const req = client.get(url, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(body));
} catch {
reject(new Error('Invalid JSON'));
}
});
});
req.on('error', reject);
});
}
private delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
function isServerError(err: any): boolean {
return err?.statusCode >= 500 || err?.code === 'ECONNRESET';
}
Разбор: Класс инкапсулирует повторные попытки и кэш. Промт требует типизированный ответ — это важно для TypeScript-проектов. Для продакшна стоит добавить обработку разных Content-Type.
4. Промт для генерации React-хука: useLocalStorage
Задача: Хук для синхронизации состояния с localStorage.
Промт:
Напиши React-хук useLocalStorage на TypeScript. Он должен принимать ключ (string) и начальное значение. Возвращать кортеж [value, setValue] как useState. setValue должен сохранять значение в localStorage и обновлять состояние. Добавь обработку ошибок (например, если localStorage заблокирован). Используй JSON.parse/stringify.
Результат:
import { useState, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((prev: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.warn(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = useCallback((value: T | ((prev: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.warn(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
Разбор: Хук использует ленивую инициализацию useState для чтения из localStorage. Функциональное обновление (value instanceof Function) поддерживает паттерн как в useState. Обработка ошибок защищает от блокировки localStorage.
5. Промт для генерации Express middleware
Задача: Middleware для валидации запросов с Joi/Zod.
Промт:
Напиши middleware для Express на TypeScript, который валидирует тело запроса с помощью Zod. Создай функцию validate, которая принимает Zod-схему и возвращает middleware. В случае ошибки возвращай 400 с сообщением об ошибке. Не используй сторонние библиотеки для middleware, только express и zod.
Результат:
import { Request, Response, NextFunction } from 'express';
import { ZodSchema, ZodError } from 'zod';
export function validate(schema: ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
try {
const parsed = schema.parse(req.body);
req.body = parsed;
next();
} catch (error) {
if (error instanceof ZodError) {
res.status(400).json({
error: 'Validation failed',
details: error.errors.map(e => ({
field: e.path.join('.'),
message: e.message,
})),
});
} else {
next(error);
}
}
};
}
Разбор: Промт явно указывает на использование Zod. Результат — чистая функция, которую можно использовать в роутах. Важно, что ошибки валидации форматируются в понятный JSON.
6. Промт для генерации тестов с Jest
Задача: Написать unit-тесты для функции debounce.
Промт:
Напиши тесты на Jest для функции debounce из предыдущего примера. Проверь: вызов после задержки, отмена через cancel, что функция не вызывается до задержки, передача аргументов. Используй fake timers.
Результат:
import { debounce } from './debounce';
jest.useFakeTimers();
describe('debounce', () => {
it('should call the function after the specified delay', () => {
const fn = jest.fn();
const debouncedFn = debounce(fn, 1000);
debouncedFn();
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(1);
});
it('should cancel the call', () => {
const fn = jest.fn();
const debouncedFn = debounce(fn, 1000);
debouncedFn();
debouncedFn.cancel();
jest.advanceTimersByTime(1000);
expect(fn).not.toHaveBeenCalled();
});
it('should pass arguments', () => {
const fn = jest.fn();
const debouncedFn = debounce(fn, 500);
debouncedFn('arg1', 42);
jest.advanceTimersByTime(500);
expect(fn).toHaveBeenCalledWith('arg1', 42);
});
it('should reset the timer on subsequent calls', () => {
const fn = jest.fn();
const debouncedFn = debounce(fn, 1000);
debouncedFn();
jest.advanceTimersByTime(500);
debouncedFn();
jest.advanceTimersByTime(500);
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(500);
expect(fn).toHaveBeenCalledTimes(1);
});
});
Разбор: Промт требует fake timers — это корректно для тестирования асинхронных задержек. Проверены все ключевые сценарии. Результат можно сразу вставить в проект.
7. Промт для генерации схемы базы данных (Prisma)
Задача: Создать модель User с связями.
Промт:
Напиши Prisma-схему для модели User. Поля: id (автоинкремент), email (уникальный), name, passwordHash, createdAt, updatedAt. Добавь связь один-ко-многим с моделью Post (id, title, content, createdAt). У Post должен быть authorId. Используй PostgreSQL.
Результат:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
createdAt DateTime @default(now())
authorId Int
author User @relation(fields: [authorId], references: [id])
}
Разбор: Промт чётко описывает поля и связи. Результат готов к использованию с Prisma CLI. Важно указать провайдера БД.
8. Промт для генерации конфигурации Webpack
Задача: Базовая конфигурация для React + TypeScript.
Промт:
Напиши конфигурацию Webpack 5 для React-приложения на TypeScript. Используй babel-loader с @babel/preset-env, @babel/preset-react, @babel/preset-typescript. Добавь поддержку CSS-модулей. Настрой dev-server с hot reload. Оптимизируй сборку: splitChunks, минификация.
Результат (сокращённо):
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
},
},
},
{
test: /\.module\.css$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { modules: true } },
],
},
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
],
devServer: {
hot: true,
port: 3000,
historyApiFallback: true,
},
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
Разбор: Промт требует конкретные пресеты и плагины. Результат — рабочая конфигурация для разработки и сборки. Для продакшна стоит добавить MiniCssExtractPlugin.
9. Промт для генерации async/await обработчика ошибок
Задача: Функция-обёртка для Express обработчиков.
Промт:
Напиши на TypeScript функцию asyncHandler, которая оборачивает асинхронный Express-обработчик. Если возникает ошибка, она должна передаваться в next(). Типизируй обработчик как RequestHandler.
Результат:
import { Request, Response, NextFunction, RequestHandler } from 'express';
export function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise<any>): RequestHandler {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
Разбор: Простой, но мощный паттерн. Промт требует типизацию — результат совместим с Express.
10. Промт для генерация React-компонента формы
Задача: Форма логина с валидацией.
Промт:
Напиши React-компонент LoginForm на TypeScript. Используй uncontrolled компоненты с ref. Валидация: email — обязательное поле формата email, password — минимум 8 символов. При отправке вызывай проп onLogin(email, password). Покажи ошибки валидации под полями. Не используй сторонних библиотек форм.
Результат:
import React, { useRef, useState } from 'react';
interface LoginFormProps {
onLogin: (email: string, password: string) => void;
}
interface Errors {
email?: string;
password?: string;
}
const LoginForm: React.FC<LoginFormProps> = ({ onLogin }) => {
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const [errors, setErrors] = useState<Errors>({});
const validate = (): boolean => {
const newErrors: Errors = {};
const email = emailRef.current?.value || '';
const password = passwordRef.current?.value || '';
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
newErrors.email = 'Введите корректный email';
}
if (!password || password.length < 8) {
newErrors.password = 'Пароль должен быть минимум 8 символов';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
onLogin(emailRef.current!.value, passwordRef.current!.value);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Email</label>
<input ref={emailRef} type="text" />
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<div>
<label>Password</label>
<input ref={passwordRef} type="password" />
{errors.password && <span style={{ color: 'red' }}>{errors.password}</span>}
</div>
<button type="submit">Login</button>
</form>
);
};
export default LoginForm;
Разбор: Промт требует uncontrolled подход. Результат — простая форма без зависимостей. Валидация на стороне клиента, но для продакшна нужна и серверная.
11. Промт для генерации скрипта миграции данных
Задача: Перенос данных из CSV в MongoDB.
Промт:
Напиши Node.js скрипт на TypeScript, который читает CSV-файл (колонки: name, email, age) и вставляет записи в MongoDB коллекцию users. Используй библиотеку csv-parser и mongoose. Обработай дубликаты по email: обновляй запись, а не создавай новую. Выведи количество обработанных записей.
Результат (сокращённо):
import mongoose from 'mongoose';
import fs from 'fs';
import csv from 'csv-parser';
const UserSchema = new mongoose.Schema({
name: String,
email: { type: String, unique: true },
age: Number,
});
const User = mongoose.model('User', UserSchema);
async function migrate(csvPath: string) {
await mongoose.connect('mongodb://localhost:27017/mydb');
const records: any[] = [];
fs.createReadStream(csvPath)
.pipe(csv())
.on('data', (row) => records.push(row))
.on('end', async () => {
let updated = 0;
for (const record of records) {
await User.findOneAndUpdate(
{ email: record.email },
{ $set: { name: record.name, age: parseInt(record.age, 10) } },
{ upsert: true }
);
updated++;
}
console.log(`Processed ${updated} records`);
await mongoose.disconnect();
});
}
migrate('users.csv').catch(console.error);
Разбор: Промт чётко описывает логику upsert. Результат — готовый скрипт для миграции.
12. Промт для генерации TypeScript-типов из JSON
Задача: Автоматическая генерация типов.
Промт:
Напиши на TypeScript функцию, которая принимает JSON-строку и возвращает строку с TypeScript-интерфейсом. Интерфейс должен называться RootObject. Для вложенных объектов создавай отдельные интерфейсы. Массивы обрабатывай как Array<тип>. Не используй сторонних библиотек.
Результат:
function jsonToTypeScript(jsonString: string): string {
const obj = JSON.parse(jsonString);
const interfaces: string[] = [];
function generateInterface(obj: any, name: string): string {
if (typeof obj !== 'object'
|| obj === null || Array.isArray(obj)) {
return typeof obj;
}
const fields: string[] = [];
for (const [key, value] of Object.entries(obj)) {
const fieldType = getType(value, key, interfaces);
fields.push(` ${key}: ${fieldType};`);
}
const iface = `interface ${name} {\n${fields.join('\n')}\n}`;
interfaces.push(iface);
return name;
}
function getType(value: any, key: string, interfaces: string[]): string {
if (value === null) return 'null';
if (Array.isArray(value)) {
if (value.length === 0) return 'any[]';
const itemType = getType(value[0], key, interfaces);
return `${itemType}[]`;
}
if (typeof value === 'object') {
const nestedName = key.charAt(0).toUpperCase() + key.slice(1);
generateInterface(value, nestedName);
return nestedName;
}
return typeof value;
}
generateInterface(obj, 'RootObject');
return interfaces.join('\n\n');
}
Разбор: Утилита полезна для быстрой генерации типов из API-ответов. Промт требует рекурсивной обработки.
Заключение
Эти 12 промтов покрывают большинство повседневных задач JavaScript/TypeScript-разработчика: от UI-компонентов до серверной логики и конфигурации. Главный секрет успешной генерации — конкретика. Указывайте типы, библиотеки, обработку ошибок и крайние случаи. Пробуйте модифицировать промты под свои нужды: добавляйте дополнительные требования, меняйте библиотеки или стиль кода. Со временем вы научитесь формулировать запросы так, что AI будет выдавать код, готовый к продакшну с минимальными правками.
Комментарии