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

Вызовется ли рендер при изменении свойства объекта записанного в useState

1.2 Junior🔥 222 комментариев
#React

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

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

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

Вызовется ли рендер при изменении свойства объекта в useState?

Ответ: НЕТ, рендер НЕ будет вызван. Это одна из самых важных концепций в React. Изменение свойства объекта без создания нового объекта не вызывает перерендер, даже если этот объект в состоянии.

Почему так работает?

React проверяет не содержимое объекта, а сам объект. Если ты изменяешь свойство существующего объекта (мутируешь его), React этого не замечает, потому что сравниваемая ссылка осталась той же.

const [user, setUser] = useState({ name: 'Alice', age: 30 });

// НЕПРАВИЛЬНО — мутация (рендер НЕ произойдёт)
user.name = 'Bob';

// ПРАВИЛЬНО — новый объект (рендер произойдёт)
setUser({ ...user, name: 'Bob' });

Практический пример

// Component с проблемой мутации
function UserProfile() {
  const [user, setUser] = useState({
    name: 'Alice',
    age: 30,
    email: 'alice@example.com',
  });

  // ПЛОХО — мутация свойства
  const handleChangeNameBad = () => {
    user.name = 'Bob';  // Изменили свойство
    console.log(user);  // { name: 'Bob', ... } — свойство изменилось
    // НО компонент НЕ перерендерится!
  };

  // ХОРОШО — создание нового объекта
  const handleChangeNameGood = () => {
    setUser({
      ...user,           // Копируем все свойства старого объекта
      name: 'Bob',       // Переопределяем только name
    });
    // Компонент перерендерится с новым user
  };

  return (
    <div>
      <p>Имя: {user.name}</p>
      <button onClick={handleChangeNameBad}>Изменить (не сработает)</button>
      <button onClick={handleChangeNameGood}>Изменить (сработает)</button>
    </div>
  );
}

Как React определяет, нужен ли перерендер?

React использует поверхностное сравнение (shallow comparison) ссылок на объекты.

const obj1 = { name: 'Alice' };
const obj2 = { name: 'Alice' };

console.log(obj1 === obj2);        // false — разные объекты
console.log(obj1.name === obj2.name);  // true — одинаковое содержимое

// React использует первое сравнение (===), а не второе
const [user, setUser] = useState(obj1);

// МУТАЦИЯ — React не перерендерит
user.name = 'Bob';  // obj1.name = 'Bob', но obj1 всё ещё одна и та же ссылка

// ПРАВИЛЬНО
const newUser = { ...user, name: 'Bob' };  // Новый объект
setUser(newUser);  // newUser !== user, React перерендерит

Примеры ошибок

1. Прямое изменение свойства:

const [data, setData] = useState({ count: 0 });

const increment = () => {
  data.count++;        // НЕПРАВИЛЬНО — мутация
  setData(data);       // React не заметит изменение
};

// Исправление
const increment = () => {
  setData({ ...data, count: data.count + 1 });  // Новый объект
};

2. Мутация массива:

const [items, setItems] = useState(['apple', 'banana']);

const addItem = () => {
  items.push('orange');  // НЕПРАВИЛЬНО — мутация
  setItems(items);       // React не заметит
};

// Исправление
const addItem = () => {
  setItems([...items, 'orange']);  // Новый массив
};

3. Изменение вложенного свойства:

const [user, setUser] = useState({
  name: 'Alice',
  profile: { age: 30, city: 'Moscow' },
});

const changeAge = () => {
  user.profile.age = 31;  // НЕПРАВИЛЬНО — мутация вложенного свойства
  setUser(user);          // React не заметит
};

// Исправление
const changeAge = () => {
  setUser({
    ...user,
    profile: { ...user.profile, age: 31 },  // Копируем и обновляем вложенный объект
  });
};

Демонстрация

function Demo() {
  const [user, setUser] = useState({ name: 'Alice', age: 30 });
  const [renderCount, setRenderCount] = useState(0);

  // Этот эффект вызывается при КАЖДОМ перерендере
  useEffect(() => {
    setRenderCount((prev) => prev + 1);
  });

  const mutateBad = () => {
    user.name = 'Bob';
    setUser(user);  // Передача того же объекта
    // renderCount НЕ изменится, потому что компонент не перерендерился
  };

  const mutateGood = () => {
    setUser({ ...user, name: 'Bob' });  // Новый объект
    // renderCount изменится, потому что компонент перерендерится
  };

  return (
    <div>
      <p>Имя: {user.name}</p>
      <p>Количество перерендеров: {renderCount}</p>
      <button onClick={mutateBad}>Мутация (не сработает)</button>
      <button onClick={mutateGood}>Обновление (сработает)</button>
    </div>
  );
}

Как правильно обновлять состояние?

Вариант 1: Spread operator (...)

const [user, setUser] = useState({ name: 'Alice', age: 30 });

setUser({ ...user, name: 'Bob' });

Вариант 2: Object.assign()

setUser(Object.assign({}, user, { name: 'Bob' }));

Вариант 3: Функциональное обновление (для сложной логики)

setUser((prevUser) => ({
  ...prevUser,
  name: 'Bob',
  updatedAt: new Date(),
}));

Вариант 4: Immer (для удобства с вложенными объектами)

import { useImmer } from 'use-immer';

const [user, setUser] = useImmer({ name: 'Alice', profile: { age: 30 } });

const updateAge = () => {
  setUser((draft) => {
    draft.profile.age = 31;  // Можно мутировать draft, Immer всё правильно обработает
  });
};

Для массивов

const [items, setItems] = useState(['apple', 'banana']);

// ПРАВИЛЬНО — создание нового массива
setItems([...items, 'orange']);           // Добавить
setItems(items.filter((i) => i !== 'apple'));  // Удалить
setItems(items.map((i) => i.toUpperCase()));   // Изменить

// НЕПРАВИЛЬНО
items.push('orange');          // Мутация
setItems(items);               // React не заметит

Зачем это нужно?

Производительность: React не может узнать, изменился ли объект изнутри, если не сравнивать содержимое (это дорого). Поэтому React полагается на то, что разработчик создаст новый объект для обновления состояния.

// Без этого правила React пришлось бы:
// 1. Глубоко сравнивать старый и новый объект
// 2. Проверять все вложенные свойства
// 3. Это было бы медленно для больших объектов

// С этим правилом:
// React просто проверяет: obj1 === obj2 (очень быстро)

Инструменты для отладки

// React DevTools показывает, какой state изменился
// Но не поймёт мутацию (если ты передал тот же объект)

// Используй консоль для отладки:
const [user, setUser] = useState({ name: 'Alice' });

const handleUpdate = () => {
  const oldRef = user;  // Сохраняем ссылку на старый объект
  
  user.name = 'Bob';    // Мутируем
  setUser(user);        // Передаём тот же объект
  
  console.log(oldRef === user);  // true — это один и тот же объект!
  // React не перерендерит
};

Резюме

При мутации свойства объекта в состоянии рендер НЕ будет вызван, потому что React проверяет только ссылку на объект (===), а не его содержимое. Для обновления состояния нужно создать новый объект с помощью spread operator (...), Object.assign(), или других способов. Это важное правило React, которое обеспечивает предсказуемое поведение и производительность. Всегда создавай новые объекты при обновлении состояния, не мутируй существующие.

Вызовется ли рендер при изменении свойства объекта записанного в useState | PrepBro