Перейти к содержимому

Шаг 0. Окружение (Vite)

Прежде чем писать логику, поднимем проект и зафиксируем сквозную модель данных. Этот код одинаков для всех шагов Части E — дальше мы только меняем компоненты.

  1. Создать проект React + TypeScript через Vite.
  2. Завести структуру папок: types/, api/, components/, hooks/.
  3. Описать тип Task и тип Filter.
  4. Написать мок-API taskApi — функции с setTimeout, имитирующие сервер (с задержкой и редкой ошибкой).
Окно терминала
npm create vite@latest task-tracker -- --template react-ts
cd task-tracker
npm install
npm run dev

Vite поднимет дев-сервер на 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.

export interface Task {
id: string; // crypto.randomUUID()
title: string; // текст задачи
done: boolean; // выполнена ли
createdAt: number; // Date.now() — для сортировки
}
export type Filter = 'all' | 'active' | 'done';

Это «сервер» в памяти. Каждая функция возвращает 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: состояние списка, где появятся первые задачи на экране.