Как оптимизировать тяжелую функцию в React с помощью ленивой инициализации?
Что проверяют
Знаете ли вы, что ленивая инициализация в React это прежде всего функция-инициализатор в useState, а не React.lazy. Следом обычно спрашивают, чем она отличается от useMemo и что будет, если начальное значение должно меняться вместе с пропсами.
Как отвечать
Покажите проблему: useState(buildIndex(items)) вызывает buildIndex на каждом рендере. React использует результат только в первый раз, дальше выбрасывает, но вычисление уже потрачено.
Решение и соседние инструменты:
useState(() => buildIndex(items)): React вызовет функцию один раз при монтировании. Можно передать и ссылку,useState(buildIndex), если функция не требует аргументов;useReducer(reducer, arg, init): третий аргумент работает так же,init(arg)вызывается только при первом рендере;useRef: встроенной ленивой формы нет, поэтому пишутif (ref.current === null) ref.current = createPlayer();useMemo(() => compute(a, b), [a, b]): другой случай, пересчёт при изменении зависимостей. Нужен, когда значение зависит от данных, а не создаётся однажды.
Если функция тяжёлая сама по себе и блокирует поток на сотни миллисекунд, мемоизация не поможет: её выносят в Web Worker или разбивают на части. Если медленно перерисовывается список при вводе, помогают useDeferredValue и startTransition. React.lazy с Suspense решает третью задачу: откладывает загрузку кода компонента, а не вычисление.
Прежде чем оптимизировать, замерьте в Profiler из React DevTools, действительно ли эта функция занимает заметное время.
Где спотыкаются
Путают ленивую инициализацию с React.lazy и начинают рассказывать про разбиение бандла. Это ответ на другой вопрос.
Ждут, что состояние обновится при смене пропса. Инициализатор срабатывает один раз, поэтому при новом items индекс останется старым. Нужен либо useMemo, либо сброс компонента через key.
Кладут в инициализатор побочные эффекты: подписки, запросы, запись в хранилище. В StrictMode при разработке React вызывает инициализатор дважды, чтобы такие ошибки проявились, а инициализатор выполняется во время рендера, где эффектам не место.