Ответ вслух Junior Код-ревью спрашивают часто обновлено

Ревью: мемоизация, которая не работает

Вопрос

Перед тобой код на ревью. Скажи вслух, что здесь не так - по убыванию важности.

Читай вслух, как на ревью: сначала что делает код, потом что сломано.

const Row = React.memo(function Row({ item, config, onSelect }) {
  return <div onClick={() => onSelect(item.id)}>{item.name}</div>;
});

function List({ items }) {
  const [selected, setSelected] = useState(null);
  return items.map((item) => (
    <Row
      key={item.id}
      item={item}
      config={{ dense: true }}
      onSelect={(id) => setSelected(id)}
    />
  ));
}

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

React.memo сравнивает пропсы поверхностно. config={{ dense: true }} создаёт новый объект на каждом рендере List, onSelect - новую функцию. Сравнение по ссылке всегда даёт «не равно», поэтому memo не пропускает ни одного рендера - зато честно тратит время на сравнение.

Что должно прозвучать в ответе

  • config - новый объект на каждом рендере, memo всегда видит разные пропсы

  • onSelect - новая стрелка на каждом рендере, та же проблема

  • memo здесь не экономит ничего, только добавляет сравнение

  • config вынести в константу за пределы компонента

  • onSelect обернуть в useCallback или передать setSelected напрямую

  • onClick внутри Row тоже создаёт стрелку, но это уже неважно

Что могут спросить следом

  • Почему setSelected можно передавать напрямую?
  • Как измерить, помогла ли мемоизация?

Разбор

React.memo сравнивает пропсы поверхностно. config={{ dense: true }} создаёт новый объект на каждом рендере List, onSelect - новую функцию. Сравнение по ссылке всегда даёт «не равно», поэтому memo не пропускает ни одного рендера - зато честно тратит время на сравнение. То есть стало медленнее, чем без него.

Починка:

const CONFIG = { dense: true };                       // вынести из компонента
const onSelect = useCallback((id) => setSelected(id), []);

setSelected из useState стабилен между рендерами, так что здесь можно передать его напрямую.

Хороший довесок на ревью: сначала стоит спросить, а нужен ли тут memo вообще. Если строк двадцать, он не нужен ни в каком виде - это оптимизация по ощущению, а не по замеру.

Ответьте сами, а не прочитайте

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