Гид по технологиям

Управление состоянием в Next с Context API — простое todo‑приложение

7 min read Frontend Обновлено 05 Jan 2026
Next.js: Context API для управления состоянием
Next.js: Context API для управления состоянием

Ноутбук с разноцветными стикерами на экране

Когда управление состоянием в приложении Next становится сложным, проблема быстро растёт. Традиционные хуки вроде useState помогают, но порождают prop drilling — передачу данных и функций через множество компонентов вниз по дереву.

Лучше разделять логику управления состоянием и сами компоненты: хранить и обновлять состояние из любой части приложения. Ниже мы пройдём пошагово, как использовать Context API, строя простое todo‑приложение.

Кого рассчитано и что нужно знать

  • Базовые знания современного JavaScript (операторы, стрелочные функции).
  • Понимание useState и деструктуризации массивов/объектов в JavaScript.
  • Node v16.8 или новее и опыт работы с npm или yarn.

Готовый проект можно найти в репозитории на GitHub — используйте ветки starter и context для сравнения.

Понимание состояния приложения (простыми словами)

Состояние приложения — это совокупность данных, которые приложение «знает» в конкретный момент времени: ввод пользователя, данные с сервера, флаги интерфейса и т. д.

Пример: счётчик может быть в следующих состояниях:

  • стандартное значение (0);
  • увеличенное на единицу;
  • уменьшенное на единицу;
  • сброшенное в значение по умолчанию.

Компонент React подписывается на изменения состояния. Взаимодействие пользователя (клики, ввод) вызывает обновления состояния, а компонент перерисовывается.

Ниже — минимальный пример счётчика, чтобы напомнить базовый паттерн useState:

const [counter, setCounter] = useState(0);  
  
return (  
  
    

{counter}

                 
);

Установка и старт проекта

Репозиторий содержит две ветки: starter (UI и заготовка) и context (готовое решение).

Клонирование starter

Выполните в терминале:

git clone -b starter https://github.com/makeuseofcode/Next.js-CRUD-todo-app.git

Установите зависимости и запустите dev‑сервер:

yarn && yarn dev

Или:

npm i && npm run dev

Если всё корректно, интерфейс должен открыться в браузере:

Исходный интерфейс приложения To‑Do

Логика: как работает Context API в приложении

Context API позволяет централизовать и шарить состояние между компонентами, избавляя от проп-дриллинга.

Шаг 1: создать и экспортировать контекст

Создайте папку src/app/context и файл todo.context.jsx внутри неё. Импортируйте createContext из react и создайте контекст:

import { createContext} from "react";  
const TodoContext = createContext();

Затем создайте кастомный хук useTodoContext, который возвращает контекст для удобного использования в компонентах:

export const useTodoContext = () => useContext(TodoContext);

(Примечание: в файле требуется также импортировать useContext из react.)

Шаг 2: создать и управлять состояниями

Для CRUD‑операций нужны состояния и провайдер, который будет их предоставлять:

const TodoContextProvider = ({ children }) => {  
  const [task, setTask] = useState("");  
  const [tasks, setTasks] = useState([]);  
  return {children};  
};  
  
export default TodoContextProvider;

Добавьте функцию handleTodoInput, которая обновляет task при вводе:

const handleTodoInput = (input) => setTask(input);

Функция createTask — добавляет новую задачу и генерирует ей простой случайный id:

const createTask = (e) => {  
    e.preventDefault();  
  
    setTasks([  
      {  
        id: Math.trunc(Math.random() * 1000 + 1),  
        task,  
      },  
      ...tasks,  
    ]);  
  };

Функция обновления задачи updateTask обновляет текст задачи по id:

const updateTask = (id, updateText) =>  
    setTasks(tasks.map((t) => (t.id === id ? { ...t, task: updateText } : t)));

Удаление задачи deleteTask фильтрует список по id:

const deleteTask = (id) => setTasks(tasks.filter((t) => t.id !== id));

Шаг 3: передать состояния и обработчики в Provider

Добавьте созданные переменные и функции в value провайдера, чтобы компоненты могли их использовать:

return (  
    
    {children}  
    
);

Шаг 4: область видимости контекста

Чтобы контекст был доступен в приложении, оберните верхний компонент провайдером. В src/app/page.jsx — оберните Todos:

  
  ;  
;

Шаг 5: использовать контекст в компонентах

В компоненте src/app/components/Todos.jsx получите значения через useTodoContext:

const { task, tasks, handleTodoInput, createTask } = useTodoContext();

Обновите форму, чтобы она использовала createTask при submit и handleTodoInput при вводе:

createTask(e)}>     handleTodoInput(e.target.value)} />   

Шаг 6: рендер задач в UI

Создайте компонент src/app/components/Todo.jsx для одной задачи. Внутри используйте updateTask и deleteTask из контекста:

import React, { useState } from "react";  
import { useTodoContext } from "../context/todo.context";  
  
const Todo = ({ task }) => {  
  const { updateTask, deleteTask } = useTodoContext();  
  
  // isEdit state tracks when a task is in edit mode  
  const [isEdit, setIsEdit] = useState(false);  
  
  return (  
    
    
      
      {isEdit ? (  updateTask(task.id, e.target.value)} /> ) :  
      ({task.task} )}  
        
      
    
  
                        
  ); }; export default Todo;

Чтобы отрисовать компонент Todo для каждой задачи, в Todos.jsx добавьте map по tasks:

{tasks && (  
  {tasks.map((task, i) => ( ))}
)}

Проверьте приложение в браузере — оно должно работать как ожидается.

Финальный интерфейс TODO: поле ввода задач сверху и две добавленные задачи ниже

Сохранение задач в localStorage

После перезагрузки страницы задачи теряются. Исправить это можно, записывая список задач в localStorage браузера и восстанавливая его при инициализации.

Ключевые шаги (без кода, чтобы не дублировать уже существующие фрагменты):

  • при старте провайдера (в useEffect с пустым массивом зависимостей) прочитать список задач из localStorage и установить в state;
  • при изменении tasks (useEffect с зависимостью [tasks]) сериализовать массив в JSON и писать в localStorage;
  • учитывать ошибку парсинга и использовать безопасный дефолт [].

Важно: localStorage доступен только в браузере — в Next.js убедитесь, что код чтения/записи выполняется на клиенте.

Важные замечания и хорошие практики

  • Генерация id через Math.random подходит для демо, но не для продакшена. Для универсальности используйте UUID (например, uuid) или id от сервера.
  • Не храните большие объёмы данных в localStorage — это синхронный API и может блокировать UI при большом объёме.
  • Валидация ввода и обработка ошибок обязательны: не полагайтесь только на required в input.

Важно: Context не заменяет архитектуру — он удобен для шаринга состояния, но не всегда лучше Redux/Zustand.

Когда Context не подходит (примеры и контрпример)

  • Масштабное приложение с тяжёлыми вычислениями и большим количеством подписчиков: при частых обновлениях всего провайдера перерисуются все подписчики — возможны проблемы с производительностью.

  • Сложная логика с побочными эффектами, транзакциями, undo/redo или сложными селекторами: лучше использовать state management с селекторами/мемоизацией или сторонние библиотеки.

Контрпример: если у вас десятки тысяч элементов в списке и частые обновления отдельных элементов, Context может привести к лишним перерисовкам. В таких случаях стоит рассмотреть локальные состояния на уровне элементов или библиотеку со строчечной подпиской (например, Zustand, Jotai) или Redux с Reselect.

Альтернативные подходы (кратко)

  • Redux — хорош для больших приложений с предсказуемостью состояния, middlewares и инструментами отладки.
  • Zustand — лёгкая библиотека с подпиской на части состояния, простая настройка и хорошая производительность.
  • Jotai/Recoil — атомарный подход к состоянию, удобен для композиции маленьких «кусочков» состояния.

Выбор зависит от требований: масштаб, требования к отладке, команда и привычки.

Ментальные модели и эвристики

  • Разделяй ответственность: UI-компоненты — только отображение; бизнес-логика — в провайдере/слое сервиса.
  • Минимизируй зону воздействия: чем меньше провайдера охватывает часть, где часто обновляются данные, тем меньше лишних перерисовок.
  • Prefer composition over prop drilling: оборачивайте части дерева, которые реально нуждаются в данных.

Факт‑бокс: ключевые концепты

  • Context API — механизм для передачи значений через дерево компонентов без prop drilling.
  • Provider — компонент, который предоставляет значение контекста всем детям.
  • useContext — хук для доступа к значению контекста.
  • CRUD — Create, Read, Update, Delete: основные операции над задачами.
  • localStorage — синхронное хранилище в браузере для простого персистирования данных.

Мини‑методология внедрения Context в проект

  1. Определите модель данных (какие поля у задачи).
  2. Создайте контекст и провайдер в отдельной папке (src/app/context).
  3. Инкапсулируйте все операции (handleInput, create, update, delete) в провайдере.
  4. Используйте кастомный хук для доступа к контексту из компонентов.
  5. Добавьте persistance (localStorage или API) и эффекты для синхронизации.
  6. Напишите простые тесты/acceptance для CRUD сценариев.

Чек‑лист по ролям

Разработчик:

  • Выделить контекст и провайдер в отдельный модуль.
  • Написать unit‑тесты для create/update/delete логики.
  • Реализовать сохранение в localStorage и обработку ошибок.

Код‑ревьювер:

  • Проверить области перерисовки и потенциальные лишние renders.
  • Оценить необходимость мемоизации или разделения провайдера.

Product Manager / ТЗ:

  • Уточнить требования по persistency (localStorage vs сервер).
  • Определить ожидаемое поведение при конфликте обновлений.

Критерии приёмки

  • Можно создать, прочитать, обновить и удалить задачу.
  • Список задач сохраняется после перезагрузки страницы (localStorage).
  • UI не ломается при пустых или некорректных данных.

Примеры тестов (acceptance)

  • Создать задачу через форму — она появляется в списке и в localStorage.
  • Отредактировать задачу — изменения видны и сохраняются.
  • Удалить задачу — элемент исчезает и не присутствует в localStorage.
  • Перезагрузить страницу — ранее добавленные задачи остаются.

Итог

Context API — удобный инструмент для упрощения распространённого паттерна prop drilling и централизации состояния в небольших и средних приложениях. Для сложных, высоконагруженных систем стоит рассмотреть специализированные библиотеки или гибридные архитектуры. В этом руководстве вы получили практическую реализацию todo‑приложения с Context, советы по персистенции в localStorage и рекомендации по улучшению производительности.

Краткие рекомендации для следующего шага: вынесите логику работы с localStorage в отдельный хук (usePersistedState), замените генерацию id на UUID в продакшене и добавьте тесты на CRUD‑операции.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как распечатать страницу в Chrome
Руководства

Как распечатать страницу в Chrome

Как отключить сохранение истории в Google Chrome
Браузеры

Как отключить сохранение истории в Google Chrome

Как использовать Samsung Pay — настройка и безопасность
Мобильные платежи

Как использовать Samsung Pay — настройка и безопасность

Как получать результаты Craigslist на email
Руководство

Как получать результаты Craigslist на email

Модуль calendar в Python — быстрое руководство
Python

Модуль calendar в Python — быстрое руководство

Категории в Outlook: как настроить и выбрать цвета
Productivity

Категории в Outlook: как настроить и выбрать цвета