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

Будешь ли использовать React Context если компоненты не ререндерятся

1.8 Middle🔥 221 комментариев
#React#Архитектура и паттерны

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

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

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

React Context и ненужные ререндеринги

Ответ: Нет, не буду использовать Context, если компоненты не должны ререндеривать. Context автоматически триггерит ререндеринг подписанных компонентов при изменении значения, что может привести к проблемам производительности. Есть альтернативные решения.

Суть проблемы

React Context по умолчанию вызывает ререндеринг всех компонентов, потребляющих контекст, даже если им нужно только прочитать значение:

const AppContext = React.createContext();

function Provider({ children }) {
  const [state, setState] = useState(0);
  
  return (
    <AppContext.Provider value={{ state, setState }}>
      {children}
    </AppContext.Provider>
  );
}

function Consumer() {
  const { state, setState } = useContext(AppContext);
  
  // Компонент ререндеривается каждый раз, когда state меняется
  console.log("Consumer ререндерен");
  
  return <div>{state}</div>;
}

// Проблема: Consumer ререндеривается, хотя ему достаточно только читать значение

Когда Context вызывает ненужные ререндеринги

Сценарий 1: Разделение логики и представления

// ❌ Плохой паттерн - все компоненты ререндеривают друг из-за друга
const AppContext = React.createContext();

function App() {
  const [userId, setUserId] = useState(null);
  const [notifications, setNotifications] = useState([]);
  const [theme, setTheme] = useState("light");
  
  return (
    <AppContext.Provider value={{
      userId, setUserId,
      notifications, setNotifications,
      theme, setTheme
    }}>
      <UserComponent />       {/* Ререндер при изменении notifications */}
      <NotifyComponent />     {/* Ререндер при изменении userId */}
      <ThemeComponent />      {/* Ререндер при изменении всего */}
    </AppContext.Provider>
  );
}

Сценарий 2: Частые обновления

function App() {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMouseMove = (e) => {
      setMousePosition({ x: e.clientX, y: e.clientY });
      // Обновляется 60+ раз в секунду!
    };
    
    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);
  
  return (
    <PositionContext.Provider value={mousePosition}>
      <Component />  {/* Ререндер 60+ раз в секунду! */}
    </PositionContext.Provider>
  );
}

Решение 1: Стабилизация значения Context

Используйте useMemo чтобы объект value не менялся, если его содержимое не изменилось:

function Provider({ children }) {
  const [state, setState] = useState(0);
  const [other, setOther] = useState("data");
  
  // ✅ Значение меняется только когда state меняется
  const value = useMemo(() => ({
    state,
    setState
  }), [state]);
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
}

Это не предотвращает ререндеринг, но предотвращает ненужные обновления других частей.

Решение 2: Разделение Context на несколько

Используйте несколько контекстов для разных данных:

// Отдельные контексты для отдельных данных
const UserContext = React.createContext();
const NotificationContext = React.createContext();
const ThemeContext = React.createContext();

function App() {
  const [userId, setUserId] = useState(null);
  const [notifications, setNotifications] = useState([]);
  const [theme, setTheme] = useState("light");
  
  const userValue = useMemo(() => ({ userId, setUserId }), [userId]);
  const notifValue = useMemo(() => ({
    notifications,
    setNotifications
  }), [notifications]);
  const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
  
  return (
    <UserContext.Provider value={userValue}>
      <NotificationContext.Provider value={notifValue}>
        <ThemeContext.Provider value={themeValue}>
          <UserComponent />    {/* Ререндер только при изменении userId */}
          <NotifyComponent />  {/* Ререндер только при изменении notifications */}
          <ThemeComponent />   {/* Ререндер только при изменении theme */}
        </ThemeContext.Provider>
      </NotificationContext.Provider>
    </UserContext.Provider>
  );
}

Решение 3: Паттерн разделения State и Dispatch

// Отдельный контекст для данных (редко меняется)
const StateContext = React.createContext();

// Отдельный контекст для функций (не меняется)
const DispatchContext = React.createContext();

function Provider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

function UseState() {
  return useContext(StateContext);
}

function UseDispatch() {
  return useContext(DispatchContext);
}

// Компонент, который только читает данные
function Reader() {
  const state = UseState();
  return <div>{state.value}</div>;
}

// Компонент, который только изменяет данные (не ререндеривается)
function Writer() {
  const dispatch = UseDispatch(); // Это не меняется!
  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Изменить
    </button>
  );
}

Решение 4: Замена Context на другое решение

Если компоненты не должны ререндеривать, рассмотрите:

Вариант 1: Zustand (рекомендуется)

import { create } from zustand;

const useAppStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

function Component() {
  // Подписывается только на count
  const count = useAppStore((state) => state.count);
  return <div>{count}</div>;
}

function Button() {
  // Подписывается только на increment
  const increment = useAppStore((state) => state.increment);
  // НЕ ререндеривается при изменении count!
  return <button onClick={increment}>+</button>;
}

Вариант 2: Jotai (атомарное состояние)

import { atom, useAtom } from jotai;

const countAtom = atom(0);

function Component() {
  const [count, setCount] = useAtom(countAtom);
  return <div>{count}</div>;
}

function Button() {
  const [, setCount] = useAtom(countAtom);
  // Компонент мемоизирует функции
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}

Вариант 3: Recoil

import { atom, useRecoilState, useSetRecoilState } from recoil;

const countAtom = atom({ key: count, default: 0 });

function Reader() {
  const [count] = useRecoilState(countAtom);
  return <div>{count}</div>;
}

function Writer() {
  const setCount = useSetRecoilState(countAtom);
  // НЕ ререндеривается при изменении count
  return <button onClick={() => setCount(c => c + 1)}>+</button>;
}

Практический пример: Неправильное использование Context

// ❌ ПЛОХО - Частые обновления вызывают ненужные ререндеринги
function AnimationProvider({ children }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const animate = () => {
      setPosition(prev => ({
        x: prev.x + 1,
        y: prev.y + 1
      }));
    };
    
    const animationId = setInterval(animate, 16);
    return () => clearInterval(animationId);
  }, []);
  
  return (
    <AnimationContext.Provider value={position}>
      {children}
    </AnimationContext.Provider>
  );
}

Практический пример: Правильное использование

// ✅ ХОРОШО - Zustand для частых обновлений
const useAnimation = create((set) => ({
  position: { x: 0, y: 0 },
  setPosition: (pos) => set({ position: pos }),
}));

function AnimationProvider({ children }) {
  const setPosition = useAnimation((state) => state.setPosition);
  
  useEffect(() => {
    const animate = () => {
      setPosition(prev => ({
        x: prev.x + 1,
        y: prev.y + 1
      }));
    };
    
    const animationId = setInterval(animate, 16);
    return () => clearInterval(animationId);
  }, []);
  
  return children;
}

function Animated() {
  const position = useAnimation((state) => state.position);
  // Компонент обновляется только по необходимости
  return <div style={position}>Анимировано</div>;
}

Таблица: Когда что использовать

СценарийРешениеПричина
Редкие обновления (тема, язык)ContextПросто, встроено в React
Частые обновления (позиция, анимация)Zustand/JotaiСелективные подписки
Много связанного состоянияZustandЛегко структурировать
Асинхронное состояниеRedux ToolkitВстроенная поддержка
Простое локальное состояниеuseStateKISS принцип

Итоговые рекомендации

  1. Для простого глобального состояния: Context
  2. Для часто меняющегося состояния: Zustand или Jotai
  3. Если часть компонентов не должна ререндеривать: Разделите контексты или используйте state management
  4. Для производительности: Всегда мемоизируйте значение контекста
  5. Сомневаетесь? Используйте Zustand — это хороший компромисс между простотой Context и производительностью Redux

Главный вывод: Context не идеален для всех сценариев. Если компоненты не должны ререндеривать при изменении данных, лучше использовать специализированные библиотеки state management, которые поддерживают селективные подписки.

Будешь ли использовать React Context если компоненты не ререндерятся | PrepBro