Интерфейс не просто «есть» или «нет». Он находится в одном из нескольких состояний: загружается, показывает данные или сообщает об ошибке. В этой лаборатории мы построим список задач, который умеет показывать все три ситуации.
Аналогия из жизни. Представьте статус посылки в интернет-магазине. Посылка может быть в трёх положениях: «в пути» (ещё едет), «доставлена» (вы её получили) или «потеряна» (произошла ошибка). В каждом случае вы видите разную картинку на сайте.
Суть. Состояние (state) — это текущее положение дел в программе. От него зависит, что именно видит пользователь на экране. Меняется состояние — меняется и интерфейс.
В нашем списке задач (он называется TaskFlow) всё точно так же: пока данные грузятся — показываем «Загрузка…», когда пришли — показываем список, если что-то сломалось — показываем ошибку и кнопку «Повторить».
Контекст. Это «карта» нашей программы. Она показывает, в каких состояниях может находиться экран и как из одного состояния можно перейти в другое.
Аналогия из жизни. Это как правила доставки посылки: из статуса «в пути» она может прийти в «доставлена» или «потеряна», а из «потеряна» — снова отправиться в «в пути» (повторная попытка). Автомат состояний (state machine) — это просто правило: «в какой момент какой экран показывать».
idle → loading → success
→ error → loading (повтор)
Пошаговый разбор схемы:
idle — начальный покой. На экране только кнопка «Загрузить задачи».loading (идёт загрузка).success (показываем список задач).error (показываем ошибку).loading.Что увидит студент. На бумаге — стрелочную схему выше. В браузере — тот экран, который соответствует текущему состоянию.
✍️ Действие: создайте файл lab2.html рядом с предыдущим (в той же папке, где лежат ваши лабораторные).
Зачем. В этом файле мы напишем весь код списка задач целиком — и HTML, и стили, и логику. Это самодостаточная страничка, которую можно просто открыть в браузере.
Контекст. Ниже — полный код страницы lab2.html. Он строит список задач TaskFlow и умеет показывать три состояния: загрузка, успех и ошибка. Весь «мозг» программы — в функции render(), которая рисует экран в зависимости от переменной status.
Аналогия из жизни. Переменная status — это текущий статус заказа: «в работе» (loading) / «готов» (success) / «ошибка» (error). В зависимости от статуса посетитель (пользователь) видит разную картину. Функция render() — это работник, который, глядя на статус, расставляет нужные вещи на витрине.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Лаборатория 2: три состояния</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px;
margin: 40px auto; padding: 20px; }
.btn { padding: 10px 20px; font-size: 15px; cursor: pointer;
border: none; border-radius: 6px; color: white; margin-top: 10px; }
.btn-primary { background: #2d5bff; }
.btn-primary:hover { background: #1e4ad0; }
.btn-secondary { background: #6c757d; }
.status-loading { color: #2d5bff; font-weight: bold; }
.status-error { color: #dc3545; }
.status-success { color: #159957; font-weight: bold; }
ul { padding-left: 20px; line-height: 1.8; }
</style>
</head>
<body>
<!-- div с id="app" — это «пустая витрина». Весь интерфейс мы будем
рисовать внутри неё из JavaScript. -->
<div id="app"></div>
<script>
// Три переменные, которые помнят текущий статус заказа:
let status = 'idle'; // текущее состояние: idle / loading / success / error
let tasks = []; // сюда положим загруженный список задач
let error = null; // текст ошибки (пусто, если всё хорошо)
// Главная функция рисования. Она смотрит на status и решает,
// что именно показать пользователю.
function render() {
// document.getElementById('app') — находим нашу «витрину» в HTML.
// DOM (объектная модель документа) — это дерево страницы,
// как скелет дома: есть главный этаж и комнаты-ветки.
const app = document.getElementById('app');
app.innerHTML = ''; // очищаем витрину перед новой отрисовкой
if (status === 'idle') {
// Покой: показываем только кнопку «Загрузить задачи».
const btn = document.createElement('button');
btn.className = 'btn btn-primary';
btn.textContent = 'Загрузить задачи';
btn.onclick = handleLoad; // по клику запускаем загрузку
app.appendChild(btn);
}
else if (status === 'loading') {
// Идёт загрузка: синяя надпись «Загрузка данных...».
const p = document.createElement('p');
p.className = 'status-loading';
p.textContent = 'Загрузка данных...';
app.appendChild(p);
}
else if (status === 'error') {
// Ошибка: красная надпись и кнопка «Повторить».
const p = document.createElement('p');
p.className = 'status-error';
p.textContent = 'Ошибка: ' + error;
app.appendChild(p);
const btn = document.createElement('button');
btn.className = 'btn btn-primary';
btn.textContent = 'Повторить';
btn.onclick = handleLoad; // снова пытаемся загрузить
app.appendChild(btn);
}
else if (status === 'success') {
// Успех: зелёная надпись, список задач и кнопка «Сбросить».
const p = document.createElement('p');
p.className = 'status-success';
p.textContent = 'Задачи загружены!';
app.appendChild(p);
// Перебираем каждую задачу и делаем из неё пункт списка <li>.
const ul = document.createElement('ul');
tasks.forEach(function(t) {
const li = document.createElement('li');
li.textContent = t.title;
ul.appendChild(li);
});
app.appendChild(ul);
const btn = document.createElement('button');
btn.className = 'btn btn-secondary';
btn.textContent = 'Сбросить';
// Сброс возвращает всё в начальное состояние idle.
btn.onclick = function() { status = 'idle'; tasks = []; render(); };
app.appendChild(btn);
}
}
// Асинхронная функция загрузки. Слово async значит:
// внутри может быть «пауза» (ожидание), не блокируя страницу.
async function handleLoad() {
status = 'loading'; // меняем статус заказа на «в пути» (loading)
error = null;
render(); // перерисовываем экран сразу
try {
// Имитируем запрос на сервер паузой в 1,5 секунды.
// HTTP-запрос — это как заказ в магазине: послали заявку, ждём посылку.
await new Promise(function(r) { setTimeout(r, 1500); });
tasks = [
{ id: 1, title: 'Изучить state machine' },
{ id: 2, title: 'Реализовать idle → loading → success' },
{ id: 3, title: 'Добавить обработку ошибки' }
];
// Чтобы увидеть состояние ошибки, раскомментируйте следующую строку:
// throw new Error('Симуляция ошибки загрузки');
status = 'success'; // данные пришли — переключаем на успех
} catch (err) {
// Сюда попадаем, если выше случилась ошибка (или раскомментировали throw).
error = err.message;
status = 'error'; // меняем статус заказа на «ошибка»
}
render(); // перерисовываем экран с новым состоянием
}
render(); // первый запуск: покажем начальный экран (idle)
</script>
</body>
</html>
Пошаговый разбор того, как работает этот код:
render(). Переменная status равна 'idle' → рисуется кнопка «Загрузить задачи».handleLoad(). Она ставит status = 'loading' и перерисовывает экран (видна надпись «Загрузка данных…»).tasks записывается список задач, status становится 'success', экран перерисовывается → видим список.throw, то вместо успеха сработает catch, status станет 'error' → видим ошибку и кнопку «Повторить».💡 Инсайт: обратите внимание — весь интерфейс рисуется ОДНОЙ функцией render(). Мы нигде не прячем и не показываем куски вручную: меняем только status, а render() сам решает, что отрисовать. Это и есть суть реактивного UI.
Откройте файл lab2.html в браузере (двойной клик по файлу). Нажмите «Загрузить задачи».
Что увидит студент. Сначала появится синяя надпись «Загрузка данных…», а через полторы секунды — зелёная «Задачи загружены!» и список из трёх пунктов.
Найдите в коде закомментированную строку с throw new Error и раскомментируйте её (уберите два слэша // в начале). Обновите страницу и снова нажмите кнопку.
Что увидит студент. Вместо списка появится красная надпись «Ошибка: Симуляция ошибки загрузки» и синяя кнопка «Повторить». Нажав её, можно снова попасть в состояние загрузки.
handleLoad, перед status = 'success'. Если ошибка вам надоест — снова поставьте // перед throw.Аналогия из жизни. Представьте, что вы строите дом. Идея одна — нужны стены, крыша, окна. Но инструменты разные: один мастер кладёт кирпич мастерком, другой — специальным пистолетом. Результат один и тот же дом, но способы разные. Так же и тут: идея «UI зависит от состояния» одна, а фреймворки дают разные инструменты.
| Концепция | В React | В Angular |
|---|---|---|
| Хранение состояния | useState | signal() |
| Условный рендеринг | {status === 'loading' && ...} | @if (status() === 'loading') { ... } |
| Изменение состояния | setStatus('loading') | this.status.set('loading') |
Сама идея одна: UI зависит от состояния. Фреймворк лишь предоставляет инструменты для хранения и отображения. В нашей лаборатории мы обошлись чистым JavaScript, чтобы вы увидели эту механику «без обёрток».
▶️ Песочница: здесь можно поиграть с кодом прямо на странице. Нажмите «Запустить» — и результат появится справа.
Контекст. В песочнице — упрощённая версия той же машины состояний. Она умеет показывать только три состояния: idle, loading и success. Попробуйте изменить начальное значение status или текст внутри app.innerHTML и запустите снова.
Аналогия из жизни. Это как смена статуса заказа: вы меняете статус посылки (например, «в пути» → «доставлена») — и у покупателя на сайте сразу меняется вся картинка. Здесь меняете состояние — и экран показывает другое.
Что увидит студент. Справа откроется маленькая страничка. В начале — кнопка «Загрузить». Нажмите её: появится «Загрузка...», затем «Готово!» и список из трёх задач. 💡 Попробуйте сами заменить let status = 'idle' на let status = 'loading' и запустите — увидите, как сразу меняется стартовый экран.
Лаборатория 2 из 5. ← Лаборатория 1 | Лаборатория 3 →
| Термин | Жизненная аналогия | Коротко |
|---|---|---|
| Состояние (state) | Статус заказа: «в обработке / готов / ошибка» | Текущее положение дел, от которого зависит экран. |
| loading (загрузка) | Посылка «в пути» | Данные ещё едут — показываем индикатор. |
| empty (пусто) | Посылка «доставлена», но коробка пуста | Данных нет — показываем заглушку «ничего нет». |
| error (ошибка) | Посылка «потеряна» | Что-то сломалось — показываем ошибку и «Повторить». |
| Сигнал (signal) | Группа в мессенджере | Переменная с подписчиками: изменилось значение — все получили сообщение. |
| Signal (Angular) | Та же группа в мессенджере | Способ хранить состояние в Angular: this.status.set('loading'). |
| Условный рендеринг | Вывеска, зависящая от статуса заказа | Показываем разные блоки в зависимости от состояния. |
1. Весь интерфейс в этой лаборатории рисуется одной функцией render(). Верно или неверно?
Ответ: Верно. Мы нигде не прячем куски вручную — меняем только status, а render() сам решает, что отрисовать. Как работник, который по статусу заказа расставляет нужные вещи на витрине.
2. Если в коде раскомментировать строку с throw new Error, программа попадёт в состояние error. Верно или неверно?
Ответ: Верно. throw выбросит ошибку, сработает catch, и статус заказа станет «потеряна» — на экране появится красная надпись и кнопка «Повторить».
3. Сигнал в Angular похож на группу в мессенджере: при изменении значения все подписчики узнают об этом. Верно или неверно?
Ответ: Верно. Сигнал — это переменная с подписчиками: поменяли значение — и интерфейс (как участники группы) сразу получил обновление и перерисовался.