Создание простого счётчика на React — пошаговое руководство

React — одна из самых популярных библиотек JavaScript для frontend. Многие компании используют React для интерфейсов, а разработчики ценят его компонентный подход и удобные хуки. Простое приложение — счётчик — отлично подходит для знакомства с базовыми понятиями React и их практическим применением.
Что вы создадите
В этом проекте вы реализуете приложение-счётчик с тремя основными функциями:
- Увеличить счёт — прибавляет 1 к текущему значению.
- Уменьшить счёт — отнимает 1 от текущего значения.
- Сброс — устанавливает значение в 0.
Кроме того, мы разберём лучшие практики, альтернативные реализации, тесты, доступность и распространённые ошибки.
Основные понятия React (коротко)
- Компоненты: переиспользуемые блоки UI. Компонент — это функция или класс, возвращающая JSX.
- Состояние (state): данные компонента, которые влияют на рендер. В функциональных компонентах управляется хуками.
- Функциональные компоненты: обычные JavaScript-функции, принимающие props и возвращающие JSX.
- Props: свойства, передаваемые от родителя к ребёнку.
- Хуки: функции, например useState и useReducer, которые дают доступ к состоянию и побочным эффектам внутри функциональных компонентов.
Короткая 1‑строчная дефиниция: useState — хук для локального состояния; useReducer — хук для сложной логики обновления состояния.
Быстрый старт — создание проекта (Step 1)
Откройте терминал и выполните:
npx create-react-app react-counter-appЗатем запустите сервер разработки:
npm startПо умолчанию приложение будет доступно на http://localhost:3000 и автоматически обновляется при изменениях.
Создаём каркас приложения (Step 2)
Откройте src/App.js и замените содержимое простым скелетом:
import React, { useState } from "react";
function App() {
const [count, setCount] = useState(0);
let incrementCount = () => {
// To add later
};
let decrementCount = () => {
// To add later
};
let resetCount = () => {
// To add later
}
return (
Count: {count}
);
}
export default App;Пояснение: useState создаёт локальное состояние count и функцию setCount для его обновления. Скобки {count} в JSX означают вставку JavaScript-значения.
Добавляем функциональность и кнопку-компонент (Step 3)
Добавьте три кнопки внутри
Теперь обновим функции, чтобы счётчик работал:
let incrementCount = () => {
setCount(count + 1);
};
let decrementCount = () => {
setCount(count - 1);
};
let resetCount = () => {
setCount(0);
}Создайте папку src/components и файл src/components/Button.js со следующим кодом:
import React from "react";
function Button(props) {
let { action, title } = props;
return ;
}
export default Button;Важно: при импорте компонента в App.js используйте корректный путь и имя файла:
import Button from "./components/Button";В исходном примере была опечатка (“Botton”). Такая ошибка вызывает импортную ошибку во время сборки — исправьте имя файла/пути.
Ниже — финальная рабочая версия App.js (корректный импорт):
import React, { useState } from "react";
import Button from "./components/Button";
function App() {
const [count, setCount] = useState(0);
let incrementCount = () => {
setCount(count + 1);
};
let decrementCount = () => {
setCount(count - 1);
};
let resetCount = () => {
setCount(0);
}
return (
Count: {count}
);
}
export default App;Улучшения и лучшие практики
Ниже — набор практик и улучшений, применимых к этому простому приложению.
1) Используйте функциональное обновление setState там, где это важно
Если будущие обновления зависят от предыдущего состояния и могут идти параллельно (например, из нескольких обработчиков), предпочтительнее использовать функциональную форму:
setCount(prev => prev + 1);
setCount(prev => prev - 1);Преимущество: исключает состояния гонки, если несколько обновлений происходят подряд.
2) aria-атрибуты и доступность (a11y)
- Кнопки должны иметь понятные метки (title/aria-label) и порядок фокуса.
- Для визуального счёта добавьте role и aria-live, чтобы ассистивные технологии знали о динамике:
Count: {count}
3) Использование useReducer для более сложной логики
Если логика обновления вырастает (несколько типов действий, сложные правила), useReducer делает код более предсказуемым:
import React, { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
Count: {state.count}
);
}useReducer полезен также для предсказуемости и тестирования.
4) TypeScript‑вариант
Если проект на TypeScript, типизация props и стейта повышает надёжность:
type ButtonProps = {
title: string;
action: () => void;
}
function Button({ title, action }: ButtonProps) {
return ;
}5) Минимальные тесты (unit)
Примеры тестов с Jest + React Testing Library:
- Проверить, что при клике на Increment счёт увеличивается.
- Проверить, что при клике на Reset счёт возвращается в 0.
Пример (псевдо-код):
render( );
fireEvent.click(screen.getByText('Increment'));
expect(screen.getByText(/Count:/)).toHaveTextContent('Count: 1');Частые ошибки и как их исправлять
- Неправильный импорт компонента: import Button from “./components/Botton” — исправьте на Button и переименуйте файл, если нужно.
- Некорректное использование состояния в асинхронном окружении — используйте функциональную форму setState.
- Отсутствие aria-live на динамическом тексте — ухудшает доступность.
- Модификация состояния напрямую (например, count++ без setCount) — нарушает принцип иммутабельности.
Дополнения по UX и структуре
- Покажите отключённую кнопку «Decrease» при значении 0, если не хотите отрицательных значений.
- Добавьте анимацию или визуальный отклик для лучшего UX.
- Держите компоненты маленькими: Button — отдельный компонент, Header или Display — отдельный.
Критерии приёмки
- Приложение запускается командой npm start и открывается на localhost:3000.
- При клике Increment значение увеличивается на 1.
- При клике Decrement значение уменьшается на 1 (или кнопка отключена при 0, если реализовано).
- При клике Reset значение становится 0.
- Компонент Button переиспользуется три раза и импортируется корректно.
- Есть базовый тест, покрывающий хотя бы один сценарий (например, Increment).
Чек-листы (роль‑ориентированные)
Разработчик:
- Создал проект create-react-app
- Имплементировал App и Button
- Использовал setState корректно
- Добавил aria-live для счётчика
- Написал один unit-тест
Кодревьювер:
- Проверил корректность импортов и имён файлов
- Проверил отсутствие мутаций состояния
- Проверил доступность (a11y)
- Проверил простоту и читаемость кода
Ментальные модели и эвристики
- Разделяй и властвуй: выделяйте UI в мелкие компоненты.
- Single source of truth: храните состояние там, где оно логически принадлежит.
- Предсказуемость изменений: отдавайте предпочтение чистым функциям и reducer-трансформациям.
Малый playbook для расширения
- Если потребуется сохранять значение между перезагрузками — используйте localStorage и синхронизацию через useEffect.
- Если количество действий вырастает — переходите на useReducer.
- Для глобального состояния (несколько страниц) — рассмотрите Context или менеджеры состояний.
Примеры расширений и когда они нужны
- Когда счётчик должен синхронизироваться между вкладками — нужен shared worker или синхронизация через backend.
- Когда состояние становится сложным (несколько полей) — useReducer предпочтительнее.
Быстрое сравнение: useState vs useReducer
- useState: простые, локальные состояния (числа, строки, небольшие объекты).
- useReducer: сложная логика обновления, много типов действий, лучше тестируется.
Небольшие советы по производительности
- Для очень частых обновлений обёрните обработчики в useCallback, если передаёте их глубоко вниз.
- Не оптимизируйте преждевременно; сначала измерьте.
Пример принятого кода с улучшениями (итоговый компактный вариант)
import React, { useState } from "react";
import Button from "./components/Button";
function App() {
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => Math.max(0, prev - 1));
const reset = () => setCount(0);
return (
Count: {count}
);
}
export default App;Короткий список тестов и критерии приёмки (технические)
- Тест 1: Increment увеличивает значение на 1.
- Тест 2: Decrement уменьшает значение на 1, но не уходит ниже 0 (если такое поведение ожидается).
- Тест 3: Reset устанавливает 0.
Заключение
Создание простого счётчика на React — отличное упражнение для освоения компонентов, props, состояния и хуков. После реализации базового варианта стоит улучшить доступность, добавить тесты и рассмотреть useReducer/TypeScript, если требования к коду усложняются. Начиная с небольшой структуры и применяя описанные практики, вы получите читаемое, тестируемое и расширяемое приложение.
Важные заметки:
- Исправляйте опечатки в путях/имёнах файлов при ошибках импорта.
- Используйте функциональную форму setState, когда обновление зависит от предыдущего состояния.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone