Чем наполнить массив, создаваемый один раз и используемый в React компоненте
Что проверяют
Понимаете ли вы, что тело компонента выполняется на каждом рендере, и знаете ли, чем обеспечить стабильную ссылку. За вопросом обычно стоит реальный баг: новый массив каждый раз попадает в зависимости эффекта или в мемоизированного ребёнка и обнуляет всю оптимизацию.
Как отвечать
Разделите два вопроса: где массив живёт и чем наполняется.
- Значения известны заранее и не зависят от props: константа вне компонента, ссылка одна на весь модуль.
- Значения выводятся из props:
useMemoс честным списком зависимостей. - Нужен изменяемый контейнер, который не влияет на разметку:
useRef. - Массив это состояние и дорого строится: ленивый инициализатор, иначе функция построения вызовется на каждом рендере, а результат будет выброшен.
- Данные приходят с сервера: пустой массив в состоянии плюс загрузка, это уже не про создание один раз.
const OPTIONS = ["новые", "популярные"];
const ids = useMemo(() => rows.map((r) => r.id), [rows]);
const [items, setItems] = useState(() => buildHeavyList());Добавьте, зачем это нужно: стабильная ссылка убирает лишние срабатывания useEffect, возвращает смысл обёртке memo у дочерних компонентов и не заставляет пересчитывать всё производное на каждый ввод символа.
Где спотыкаются
Пишут инициализатор вызовом, а не функцией. Такой вызов происходит на каждом рендере, React игнорирует результат после первого, и дорогая работа делается молча и постоянно, при этом внешне ничего не ломается.
Второе: считают useMemo гарантией. Это подсказка оптимизации, React вправе выбросить кэш, поэтому на нём нельзя держать то, без чего код сломается: единственный экземпляр подписки, соединение, счётчик попыток.
Третье: берут индекс элемента как key. Пока список статичен, разницы не видно, но после вставки в начало или сортировки React переиспользует не те узлы, и локальное состояние внутри строк уезжает к соседям.