Шаг 0. Окружение (Vite)
Прежде чем писать логику, поднимем проект и зафиксируем сквозную модель данных. Этот код одинаков для всех шагов Части E — дальше мы только меняем компоненты.
Постановка задачи
Заголовок раздела «Постановка задачи»- Создать проект React + TypeScript через Vite.
- Завести структуру папок:
types/,api/,components/,hooks/. - Описать тип
Taskи типFilter. - Написать мок-API
taskApi— функции сsetTimeout, имитирующие сервер (с задержкой и редкой ошибкой).
Создание проекта
Заголовок раздела «Создание проекта»npm create vite@latest task-tracker -- --template react-tscd task-trackernpm installnpm run devVite поднимет дев-сервер на http://localhost:5173. Шаблон react-ts сразу даёт React 18 + TypeScript + строгий tsconfig.
Структура папок
Заголовок раздела «Структура папок»task-tracker/ src/ types/ task.ts — interface Task, type Filter api/ taskApi.ts — мок-сервер (setTimeout) components/ TaskApp.tsx — корневой компонент приложения TaskList.tsx — список задач TaskItem.tsx — одна задача AddTaskForm.tsx — форма добавления hooks/ — кастомные хуки (появятся на шаге 6) App.tsx main.tsxСоздавать все файлы заранее не нужно — добавляй по мере шагов. Сейчас важны только types/task.ts и api/taskApi.ts.
Тип задачи — src/types/task.ts
Заголовок раздела «Тип задачи — src/types/task.ts»export interface Task { id: string; // crypto.randomUUID() title: string; // текст задачи done: boolean; // выполнена ли createdAt: number; // Date.now() — для сортировки}
export type Filter = 'all' | 'active' | 'done';Мок-API — src/api/taskApi.ts
Заголовок раздела «Мок-API — src/api/taskApi.ts»Это «сервер» в памяти. Каждая функция возвращает Promise и ждёт через setTimeout, имитируя сеть. getTasks иногда бросает ошибку — чтобы было на чём тренировать обработку error.
import type { Task } from '../types/task';
// Имитация хранилища на сервереlet serverTasks: Task[] = [ { id: 's1', title: 'Прочитать про хуки', done: true, createdAt: Date.now() - 30000 }, { id: 's2', title: 'Сделать практикум', done: false, createdAt: Date.now() - 20000 }, { id: 's3', title: 'Переписать на MobX', done: false, createdAt: Date.now() - 10000 },];
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
export const taskApi = { // Загрузка всех задач. ~700мс задержка, ~15% шанс ошибки (для тренировки error-handling) async getTasks(signal?: AbortSignal): Promise<Task[]> { await delay(700); if (signal?.aborted) throw new DOMException('Aborted', 'AbortError'); if (Math.random() < 0.15) throw new Error('Сервер недоступен (мок-ошибка)'); return structuredClone(serverTasks); },
// Создание задачи. ~400мс async addTask(title: string): Promise<Task> { await delay(400); const task: Task = { id: crypto.randomUUID(), title, done: false, createdAt: Date.now(), }; serverTasks = [...serverTasks, task]; return structuredClone(task); },
// Обновление (toggle / переименование). ~300мс async updateTask( id: string, patch: Partial<Pick<Task, 'title' | 'done'>>, ): Promise<Task> { await delay(300); const idx = serverTasks.findIndex((t) => t.id === id); if (idx === -1) throw new Error('Задача не найдена'); serverTasks[idx] = { ...serverTasks[idx], ...patch }; return structuredClone(serverTasks[idx]); },
// Удаление. ~300мс async deleteTask(id: string): Promise<void> { await delay(300); serverTasks = serverTasks.filter((t) => t.id !== id); },};Готово к практике
Заголовок раздела «Готово к практике»Теперь есть проект, тип Task и taskApi. Дальше — Шаг 1: состояние списка, где появятся первые задачи на экране.