Ответ вслух Middle Код-ревью обновлено

Ревью: большой список с фильтром

Вопрос

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

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

function Table({ rows, query }) {
  const filtered = rows.filter((r) => r.name.toLowerCase().includes(query.toLowerCase()));
  const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));

  return (
    <tbody>
      {sorted.map((row) => (
        <Row key={row.id} row={row} onClick={() => open(row.id)} />
      ))}
    </tbody>
  );
}

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

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

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

Коротко

Мутация пропсов. filter возвращает новый массив - но только если что-то отфильтровал. При пустом query это по-прежнему новый массив, а вот sort мутирует его на месте; хуже, если бы фильтра не было вовсе.

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

  • sort мутирует filtered - а при пустом query это тот же массив, что rows: мутация пропсов

  • toLowerCase от query вычисляется на каждой строке - вынести из цикла

  • фильтрация и сортировка на каждом рендере - нужен useMemo при больших объёмах

  • новая стрелка onClick на каждую строку убивает мемоизацию Row

  • нет виртуализации для тысяч строк

  • localeCompare без указания локали и без кэшированного Intl.Collator

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

  • Как вы поймёте, что useMemo здесь действительно нужен?
  • Чем плох localeCompare в цикле?

Разбор

Мутация пропсов. filter возвращает новый массив - но только если что-то отфильтровал. При пустом query это по-прежнему новый массив, а вот sort мутирует его на месте; хуже, если бы фильтра не было вовсе. Правильный тон на ревью: sort мутирует, поэтому [...filtered].sort(...) или toSorted.

Лишняя работа в цикле. query.toLowerCase() пересчитывается для каждой строки - вынести наружу.

Каждый рендер. Фильтрация и сортировка выполняются заново даже при изменении не связанного состояния; на тысячах строк это заметно. useMemo по [rows, query].

Мемоизация ломается. Новая стрелка onClick - новый проп на каждый рендер, Row в memo бесполезен. Передавать row.id и один стабильный обработчик.

Масштаб. Больше пары тысяч строк - виртуализация.

Тонкое: localeCompare без локали работает по настройкам среды и медленный - для сортировки лучше один Intl.Collator снаружи.

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

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