← Назад к вопросам

На чем лучше построить изменение темы: useContext или Redux

2.2 Middle🔥 181 комментариев
#React#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

useContext vs Redux для управления темой

Это частый вопрос при выборе архитектуры для global state. Оба подхода работают, но каждый имеет свои плюсы и минусы. Разберёмся, когда выбирать что.

useContext + useState: простое решение

// createThemeContext.js
import { createContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

// Использование
function Button() {
  const { theme, toggleTheme } = useTheme();
  return (
    <button onClick={toggleTheme}>
      Текущая тема: {theme}
    </button>
  );
}

// В App.js
function App() {
  return (
    <ThemeProvider>
      <Button />
    </ThemeProvider>
  );
}

Redux: более сложное решение

// themeSlice.js
import { createSlice } from '@reduxjs/toolkit';

const themeSlice = createSlice({
  name: 'theme',
  initialState: { theme: 'light' },
  reducers: {
    toggleTheme: (state) => {
      state.theme = state.theme === 'light' ? 'dark' : 'light';
    },
    setTheme: (state, action) => {
      state.theme = action.payload;
    }
  }
});

export const { toggleTheme, setTheme } = themeSlice.actions;
export default themeSlice.reducer;

// store.js
import { configureStore } from '@reduxjs/toolkit';
import themeReducer from './themeSlice';

const store = configureStore({
  reducer: {
    theme: themeReducer
  }
});

export default store;

// Использование
function Button() {
  const theme = useSelector(state => state.theme.theme);
  const dispatch = useDispatch();
  
  return (
    <button onClick={() => dispatch(toggleTheme())}>
      Текущая тема: {theme}
    </button>
  );
}

// В main.js
function App() {
  return (
    <Provider store={store}>
      <Button />
    </Provider>
  );
}

Сравнение: теория

useContext:

  • Встроено в React, без доп. библиотек
  • Может привести к ненужным ре-рендерам
  • Простая кривая обучения
  • Подходит для простых данных

Redux:

  • Мощный и масштабируемый
  • Предсказуемый и отлаживаемый (DevTools)
  • Требует много boilerplate кода
  • Идеален для сложного state

Проблемы useContext с темой

Проблема 1: Ненужные ре-рендеры

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null); // Другое состояние
  
  const value = { theme, setTheme, user, setUser };
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

// Любой компонент, который использует ThemeContext
function Button() {
  const { theme } = useContext(ThemeContext);
  // Когда меняется user — Button ре-рендерится, хотя theme не изменилась!
  return <button>{theme}</button>;
}

Решение 1: Разделить контексты

const ThemeContext = createContext();
const UserContext = createContext();

function App() {
  return (
    <ThemeProvider>
      <UserProvider>
        <Button /> // Теперь изолировано
      </UserProvider>
    </ThemeProvider>
  );
}

Решение 2: useCallback + useMemo

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);
  
  // Memo для theme отдельно
  const themeValue = useMemo(() => ({
    theme,
    setTheme
  }), [theme]);
  
  const userValue = useMemo(() => ({
    user,
    setUser
  }), [user]);
  
  return (
    <ThemeContext.Provider value={themeValue}>
      <UserContext.Provider value={userValue}>
        {children}
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Проблема 2: DevTools и debugging

Redux DevTools показывает все действия и изменения state. useContext не предоставляет такой информации:

// Redux DevTools
// Action: THEME/toggleTheme
// Old State: { theme: 'light' }
// New State: { theme: 'dark' }
// Difference: { theme changed from light to dark }

// useContext
// Просто re-render, сложно отследить что изменилось

Когда использовать useContext

useContext идеален для:

  1. Простая тема (только 2-3 состояния)
function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);
  
  return (
    <ThemeContext.Provider value={{ isDark, setIsDark }}>
      {children}
    </ThemeContext.Provider>
  );
}
// Достаточно простой
  1. Небольшой проект (5-10 компонентов)

  2. Не нужны DevTools и time-travel debugging

  3. Нет сложного state management

// Например, просто проксирование одного значения
const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(
    localStorage.getItem('theme') || 'light'
  );
  
  const toggleTheme = (newTheme) => {
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

Когда использовать Redux

Redux идеален для:

  1. Большой проект (100+ компонентов)

  2. Сложная логика темы (переходы, анимации, системная тема)

const themeSlice = createSlice({
  name: 'theme',
  initialState: {
    theme: localStorage.getItem('theme') || 'light',
    transition: false,
    systemPreference: window.matchMedia('(prefers-color-scheme: dark)').matches
  },
  reducers: {
    setTheme: (state, action) => {
      state.theme = action.payload;
      state.transition = true;
    },
    syncSystemTheme: (state) => {
      const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      state.theme = isDark ? 'dark' : 'light';
    }
  }
});
  1. Нужны DevTools, отладка, история действий

  2. Много других глобальных состояний (auth, notifications, filters)

// Одного Redux store хватает для всего
const store = configureStore({
  reducer: {
    theme: themeReducer,
    auth: authReducer,
    notifications: notificationsReducer,
    ui: uiReducer
  }
});
  1. Нужна предсказуемость (тестирование, воспроизведение ошибок)

Рекомендация

Таблица принятия решения:

Особенность              | useContext | Redux
----------------------------------------------------
Простая тема            | ✓ ✓ ✓      | OK
Большой проект          | ? ?        | ✓ ✓ ✓
Много состояния         | OK         | ✓ ✓ ✓
DevTools нужны          | No         | ✓ ✓ ✓
Быстро настроить        | ✓ ✓ ✓      | No
Мало зависимостей       | ✓ ✓ ✓      | No
Отладка сложная         | Hard       | ✓ ✓ ✓
Тестирование           | OK         | ✓ ✓ ✓

Практическая рекомендация

Начни с useContext:

// Просто, работает, хватает для большинства проектов
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
    localStorage.setItem('theme', theme);
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

Если возникнут проблемы с производительностью или отладкой — переходи на Redux.

Или используй Zustand (middle ground):

// Меньше boilerplate чем Redux, больше мощи чем useContext
import { create } from 'zustand';

const useTheme = create((set) => ({
  theme: 'light',
  toggleTheme: () => set(state => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  }))
}));

function Button() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>{theme}</button>;
}

Заключение

Для управления темой:

  1. Маленький проект → useContext
  2. Большой проект или много state → Redux
  3. Хочешь middle ground → Zustand

Совет: Не переусложняй. useContext + localStorage часто достаточно для управления темой. Используй Redux, только если действительно нужна его мощь.

На чем лучше построить изменение темы: useContext или Redux | PrepBro