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

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

6 min read Frontend Обновлено 30 Dec 2025
Next.js: Context API для To‑Do — управление состоянием
Next.js: Context API для To‑Do — управление состоянием

Ноутбук с множеством стикеров на экране

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

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

Что нужно знать заранее

Перед началом убедитесь, что у вас есть:

  • Базовые знания современных операторов JavaScript и хука useState в React.
  • Понимание деструктуризации массивов и объектов в JavaScript.
  • Node.js версии 16.8+ и опыт работы с npm или yarn.

Готовый проект доступен в репозитории GitHub для справки.

Понимание состояния приложения

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

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

Простой компонент счётчика, который обновляет состояние по клику:

import { useState } from "react";

const Counter = () => {
  const [counter, setCounter] = useState(0);

  return (
    

{counter}

); }; export default Counter;

Важно: при увеличении количества компонентов и связей между ними хранить состояние локально специфично и быстро приводит к prop drilling.

Установка и подготовка

Репозиторий содержит две ветки: starter и context. Ветка starter — это UI, на котором мы сосредоточимся при добавлении логики. Ветка context — финальный пример.

Клонирование стартового приложения

Откройте терминал и выполните:

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

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

# с yarn
yarn && yarn dev

# или с npm
npm i && npm run dev

Если всё в порядке, откроется UI в браузере:

Начальный интерфейс списка задач

Реализация логики с Context API

Context API даёт централизованное место для управления состоянием и методов, которыми могут пользоваться любые дочерние компоненты без передачи пропсов через дерево.

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

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

import React, { createContext, useContext } from "react";

const TodoContext = createContext();

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

export default TodoContext;

Кратко: createContext создаёт контейнер для значения; хук useTodoContext упрощает доступ к контексту в компонентах.

Шаг 2: состояния и CRUD‑операции

Создайте провайдер, в котором будут состояния и функции для CRUD. Пример базовой структуры провайдера:

import React, { useState } from "react";
import TodoContext from "./todo.context";

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

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

  const createTask = (e) => {
    e.preventDefault();

    setTasks([
      {
        id: Math.trunc(Math.random() * 1000 + 1),
        task,
      },
      ...tasks,
    ]);

    setTask("");
  };

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

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

  return (
    
      {children}
    
  );
};

export default TodoContextProvider;

Пояснение: мы храним текущую вводимую задачу в task и массив задач в tasks. Все операции (создать/обновить/удалить) модифицируют tasks.

Шаг 3: обёртка приложения провайдером

Чтобы контекст был доступен в любом месте приложения, оберните корневой компонент. В src/app/page.jsx импортируйте провайдер и используйте его:

import TodoContextProvider from "./context/todo.context";
import Todos from "./components/Todos";

export default function Page() {
  return (
    
      
    
  );
}

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

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

import { useTodoContext } from "../context/todo.context";
import Todo from "./Todo";

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

  return (
    
createTask(e)}> handleTodoInput(e.target.value)} />
{tasks && (
{tasks.map((taskItem, i) => ( ))}
)}
); }; export default Todos;

Компонент Todo (src/app/components/Todo.jsx) управляет одной задачей:

import React, { useState } from "react";
import { useTodoContext } from "../context/todo.context";

const Todo = ({ task }) => {
  const { updateTask, deleteTask } = useTodoContext();
  const [isEdit, setIsEdit] = useState(false);

  return (
    
          {isEdit ? (
             updateTask(task.id, e.target.value)}
            />
          ) : (
            
          )}
          
{task.task}
); }; export default Todo;

Итоговый интерфейс TODO с полем ввода и списком задач

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

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

При перезагрузке страницы массив tasks сбрасывается. Решение — сохранять задачи в localStorage и восстанавливать их при загрузке.

Пример расширения провайдера с localStorage:

import React, { useState, useEffect } from "react";
import TodoContext from "./todo.context";

const LOCAL_KEY = "my-todos";

const TodoContextProvider = ({ children }) => {
  const [task, setTask] = useState("");
  const [tasks, setTasks] = useState(() => {
    try {
      const raw = typeof window !== "undefined" ? localStorage.getItem(LOCAL_KEY) : null;
      return raw ? JSON.parse(raw) : [];
    } catch (e) {
      console.error("Ошибка чтения localStorage", e);
      return [];
    }
  });

  useEffect(() => {
    try {
      localStorage.setItem(LOCAL_KEY, JSON.stringify(tasks));
    } catch (e) {
      console.error("Ошибка записи в localStorage", e);
    }
  }, [tasks]);

  // ... остальные функции (handleTodoInput, createTask, updateTask, deleteTask)

  return (
    
      {children}
    
  );
};

export default TodoContextProvider;

Заметки по безопасности и совместимости: localStorage синхронен и ограничен по объёму. Для больших объёмов данных или серверной синхронизации рассмотрите IndexedDB или бэкенд‑хранилище.

Important: при SSR (Server‑Side Rendering) в Next.js нужно проверять наличие window перед обращением к localStorage. Инициализацию состояния с чтением localStorage выполняйте в useEffect или используйте ленивую инициализацию useState с защитой от undefined window.

Когда Context API — хорошее решение, а когда нет

  • Подходит, когда вам нужно поделиться данными между многими компонентами без сложной логики изменений.
  • Не подходит для очень частых обновлений с большими деревьями компонентов — возможны лишние ререндеры. В таких случаях подумайте о useReducer + memo или внешних решениях (Zustand, Redux).

Контрпример: список в реальном времени с тысячами элементов и частыми изменениями состояния — Context может вызвать заметные перформанс‑проблемы.

Альтернативы и масштабирование

  • useReducer + Context — хорош для сложных переходов состояния и уменьшения побочных эффектов.
  • Zustand, Jotai — лёгкие стораджи без лишних ререндеров.
  • Redux Toolkit — когда нужно централизованное предсказуемое состояние с DevTools и middleware.

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

Плейбук: пошаговая методика внедрения Context в проект

  1. Выделите гранулированные домены состояния (например: todos, auth, ui).
  2. Создайте отдельные контексты для независимых доменов.
  3. Минимизируйте shape value: отдавайте только то, что нужно компонентам.
  4. Используйте memo (useMemo, React.memo) и селекторы, чтобы избежать лишних ререндеров.
  5. Напишите тесты на основные операции CRUD.
  6. Добавьте persist (localStorage или backend) по необходимости.

Проверка и критерии приёмки

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

  • Можно добавить задачу через форму.
  • Задача отображается в списке сразу после добавления.
  • Можно редактировать существующую задачу; изменения сохраняются.
  • Можно удалить задачу и она исчезает из списка.
  • После перезагрузки задачи восстанавливаются из localStorage.
  • Все операции не приводят к ошибкам в консоли.

Тесты и случаи приёмки

  • Добавление: отправить форму с текстом -> задача появляется в начале списка.
  • Редактирование: включить режим редактирования, поменять текст, сохранить -> текст изменён.
  • Удаление: удалить задачу -> задача отсутствует в DOM и localStorage.
  • Сессия: после перезагрузки список совпадает с state до перезагрузки.

Чеклист для роли разработчика и ревьюера

Для разработчика:

  • Создал TodoContextProvider с нужными методами.
  • Использовал ленивую загрузку из localStorage.
  • Обработал случай отсутствия window для SSR.
  • Обновления state атомарны и читаемы.

Для ревьюера:

  • Проверил отсутствие лишних ререндеров (React DevTools).
  • Убедился в корректной сериализации в localStorage.
  • Проверил обработку ошибок при чтении/записи в хранилище.

Краткая методология выбора решения (Decision tree)

flowchart TD
  A[Нужно поделиться состоянием между компонентами?] -->|Да| B{Частые обновления?}
  A -->|Нет| Z[Хранить локально в компоненте]
  B -->|Да| C{Объём и сложность данных}
  B -->|Нет| D[Context API подходит]
  C -->|Простые данные| E[useReducer + Context]
  C -->|Сложные/много операций| F[Redux Toolkit или Zustand]

Краткий глоссарий

  • Context API — механизм React для передачи данных через дерево компонентов без пропсов.
  • provider — компонент, который предоставляет значение контекста дочерним компонентам.
  • prop drilling — передача пропсов через многие уровни компонентов.
  • SSR — server‑side rendering.

Миграционные заметки и лучшие практики

  • Если проект станет крупным, выделяйте отдельные контексты по доменам (auth, todos, ui) вместо одного «глобального» контекста.
  • Для оптимизации используйте селекторы или мемоизацию значений value:
const value = useMemo(() => ({ task, tasks, handleTodoInput, createTask, updateTask, deleteTask }), [task, tasks]);
  • Избегайте передачи функций, которые создаются в каждом рендера, без мемоизации.

Риски и смягчения

  • Риск: лишние ререндеры при частых обновлениях. Смягчение: мемоизация, разбиение контекста на части.
  • Риск: потеря данных при ошибке localStorage. Смягчение: try/catch и fallback в памяти.

Итог

Context API — простой и удобный инструмент для совместного использования состояния между компонентами. Для небольших и средних приложений он часто решает проблему prop drilling и ускоряет разработку. При росте приложения учитывайте производительность и возможные альтернативы, такие как useReducer, Zustand или Redux.

Краткие рекомендации:

  • Начинайте с небольших контекстов по доменам.
  • Мемоизируйте value, если нужно оптимизировать ререндеры.
  • Храните persist‑данные аккуратно и проверяйте SSR.

Спасибо за чтение — теперь вы можете реализовать To‑Do приложение в Next.js с централизованным управлением состоянием и сохранением задач между сессиями.

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

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

Как сохранить Excel в PDF — быстро и без ошибок
Office

Как сохранить Excel в PDF — быстро и без ошибок

Как открыть и извлечь ISO в Linux
Linux

Как открыть и извлечь ISO в Linux

Snapseed: полное руководство по мобильному редактированию
Фотография

Snapseed: полное руководство по мобильному редактированию

Как выпустить музыку на Spotify и Apple Music
Музыка

Как выпустить музыку на Spotify и Apple Music

VPN для PS4: настройка и советы
Гайды

VPN для PS4: настройка и советы

Google Home: настройка и устранение проблем
Умный дом

Google Home: настройка и устранение проблем