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

Как изменение states в одном месте в React влияло на рендеринг?

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

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

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

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

React: управление состоянием и оптимизация рендеринга

Это вопрос о том, как React обновляет компоненты при изменении состояния и какие есть инструменты для контроля этого процесса. Давайте разберём несколько сценариев.

Базовый механизм: useState и re-render

Когда вызывается setter функция от useState, React помечает компонент на обновление и запускает re-render:

function Counter() {
  const [count, setCount] = useState(0);
  
  // Каждый раз при setCount() этот компонент перерисуется
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// Что происходит:
// 1. Пользователь кликает кнопку
// 2. Вызывается setCount(1)
// 3. React обновляет state
// 4. Компонент переразбирается (выполняется функция заново)
// 5. React сравнивает старый и новый вывод (reconciliation)
// 6. Обновляет только изменённые части DOM

Проблема: Cascade updates (каскадные обновления)

Если состояние находится высоко в дереве компонентов, его изменение может вызвать re-render множества дочерних компонентов:

// App.js
function App() {
  const [data, setData] = useState({ user: null });
  
  return (
    <div>
      <Header data={data} />
      <MainContent data={data} />
      <Sidebar data={data} />
      <Footer data={data} />
      {/* Изменение data -> перерисовка всех дочерних компонентов */}
    </div>
  );
}

Решение 1: Context для изолированного состояния

Вместо поднятия состояния вверх, используй Context для локального управления:

// contexts/UserContext.js
const UserContext = React.createContext();

export function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

export function useUser() {
  return useContext(UserContext);
}

// App.js
function App() {
  return (
    <UserProvider>
      <Header /> {/* Только это перерисуется при изменении user */}
      <MainContent />
      <Sidebar />
    </UserProvider>
  );
}

// Header.js
function Header() {
  const { user } = useUser(); // Подписывается только на user
  // Перерисуется только если user изменился
}

Решение 2: useMemo для мемоизации

useMemo предотвращает пересчёт дорогих вычислений при re-render:

function UserProfile({ userId }) {
  const [filter, setFilter] = useState('');
  
  // Это вычисляется заново при КАЖДОМ re-render
  // Даже если userId не изменилась
  const userData = fetchUserData(userId);
  
  return (
    <div>
      <p>{userData.name}</p>
      <input 
        value={filter} 
        onChange={(e) => setFilter(e.target.value)}
      />
    </div>
  );
}

// Оптимизация с useMemo
function UserProfile({ userId }) {
  const [filter, setFilter] = useState('');
  
  const userData = useMemo(
    () => fetchUserData(userId),
    [userId] // Пересчитывается только если userId изменился
  );
  
  return (
    <div>
      <p>{userData.name}</p>
      <input 
        value={filter} 
        onChange={(e) => setFilter(e.target.value)}
      />
    </div>
  );
}

Решение 3: React.memo для компонентов

React.memo предотвращает re-render компонента если его props не изменились:

function UserCard({ user }) {
  // Этот компонент перерисуется при любом re-render родителя
  return <div>{user.name}</div>;
}

// Оптимизация
const MemoizedUserCard = React.memo(UserCard);

// Теперь UserCard перерисуется только если user prop изменился
// (по shallow comparison)

function App() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: 'John' });
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <MemoizedUserCard user={user} />
      {/* Не перерисуется при изменении count */}
    </div>
  );
}

Решение 4: useCallback для стабильных функций

Когда передаёшь функцию как props, убедись что она стабильна, иначе child компоненты будут перерисовываться:

function Parent() {
  const [count, setCount] = useState(0);
  
  // Эта функция переосздаётся при каждом re-render
  const handleClick = () => console.log('clicked');
  
  return <Child onClickHandler={handleClick} />;
}

const Child = React.memo(({ onClickHandler }) => {
  // Этот компонент перерисуется КАЖДЫЙ раз
  // потому что onClickHandler это разные функции
  return <button onClick={onClickHandler}>Click</button>;
});

// Оптимизация
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(
    () => console.log('clicked'),
    [] // зависимости - функция создаётся один раз
  );
  
  return <Child onClickHandler={handleClick} />;
}

Батчинг (Automatic Batching)

React 18 автоматически батчит несколько setState вызовов:

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    // React 18: это батчится в один render
    setName('');      // Вызов 1
    setEmail('');     // Вызов 2
    // -> Компонент перерисуется один раз после обоих setState
    
    // А до React 18:
    // -> Компонент бы перерисовался дважды (по одному для каждого setState)
    
    // В асинхронных операциях батчинг работает автоматически
    const response = await fetch('/api/submit');
    setStatus('success'); // Батчится со следующим setState
  };
}

Сравнение стратегий

Подход                    | Сложность | Эффект         | Когда использовать
------------------
Context API               | средняя   | Отличный       | Изолированное состояние
useMemo                   | низкая    | Хороший        | Дорогие вычисления
React.memo                | низкая    | Хороший        | Дорогие компоненты
useCallback               | низкая    | Хороший        | Передача функций в props
useReducer                | высокая   | Отличный       | Сложное состояние
External library (Zustand)| средняя   | Отличный       | Global state

Вывод: изменение состояния в React вызывает re-render компонента и его дочерних элементов. Используй Context, useMemo, React.memo, useCallback для оптимизации и ограничения каскадных обновлений.

Как изменение states в одном месте в React влияло на рендеринг? | PrepBro