> Почему в React useState для initial value лучше передавать функцию, а не результат вызова функции? (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Домклик, Софт Медиа Групп, Сбер

Стек: React

> Пример ответа

Короткий ответ

В React передача функции в useState (lazy initialization) гарантирует, что дорогостоящее вычисление начального значения выполняется только один раз - при первом рендере компонента. Если передать результат вызова функции, вычисление будет происходить при каждом рендере, даже если результат не используется. Это оптимизация производительности для тяжелых вычислений, чтения из localStorage или парсинга JSON.

Подробное объяснение

React useState принимает либо значение напрямую, либо функцию-инициализатор. Разница критична для производительности:

  • Прямое значение: useState(computeExpensiveValue()) - функция вызывается на каждом рендере, хотя React использует результат только при монтировании. Это происходит потому, что JavaScript вычисляет аргументы перед вызовом функции.

  • Функция-инициализатор: useState(() => computeExpensiveValue()) - React вызывает эту функцию только один раз, при первом рендере компонента. Это lazy initialization.

Механизм работы: React хранит переданную функцию в замыкании и вызывает её только при инициализации состояния. При последующих ререндерах функция игнорируется, используется уже сохранённое значение.

Это особенно важно для:

  • Тяжелых вычислений (сортировка больших массивов, сложные математические операции)
  • Чтения из внешних источников (localStorage, sessionStorage)
  • Парсинга JSON или других форматов
  • Создания сложных объектов (например, экземпляров классов)

На практике

В реальных проектах lazy initialization используют для:

  • Инициализации состояния из localStorage: useState(() => JSON.parse(localStorage.getItem('key')))
  • Вычислений на основе props, которые не меняются: useState(() => heavyComputation(props.initialData))
  • Создания уникальных идентификаторов или временных меток при монтировании
  • Инициализации сложных структур данных (графы, деревья)

Для простых значений (числа, строки, булевы) разница незаметна, и можно использовать прямое значение. Но при работе с данными из внешних источников или тяжелыми вычислениями функция-инициализатор - best practice.

Важно: функция должна быть чистой (pure) - без side effects и зависимостей от внешнего состояния, которое может измениться между рендерами.

Пример кода

JSX
// ❌ Плохо - вычисление при каждом рендере
const [data, setData] = useState(JSON.parse(localStorage.getItem('data')));
// ✅ Хорошо - вычисление только при монтировании
const [data, setData] = useState(() => JSON.parse(localStorage.getItem('data')));
// ❌ Плохо - тяжелая сортировка на каждый рендер
const [sortedItems, setSortedItems] = useState(heavySort(items));
// ✅ Хорошо - сортировка только при первом рендере
const [sortedItems, setSortedItems] = useState(() => heavySort(items));
// Пример с вычислением на основе props
function UserList({ initialUsers }) {
// ✅ Безопасно - вычисление только при монтировании
const [users, setUsers] = useState(() =>
initialUsers.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`
}))
);
}

Как отвечать на собеседовании

Начни с ключевой идеи: lazy initialization для оптимизации производительности. Объясни разницу между передачей значения и функции. Приведи конкретные примеры из практики - localStorage, тяжелые вычисления. Подчеркни, что для простых значений разницы нет, но для дорогих операций это важно. Упомяни, что функция должна быть чистой. Если спросят про классовые компоненты - проведи параллель с вычислением начального state в конструкторе.

Что проверяет интервьюер

  • Понимание механизма работы React hooks и жизненного цикла компонента
  • Знание оптимизаций производительности в React
  • Понимание разницы между инициализацией и обновлением состояния
  • Умение применять best practices в реальных сценариях
  • Понимание JavaScript - как вычисляются аргументы функций

Типичные ошибки

  • Утверждение, что функция-инициализатор всегда лучше - для простых значений это избыточно
  • Использование асинхронных операций внутри функции-инициализатора - она должна быть синхронной и чистой
  • Передача стрелочной функции с зависимостями от внешних переменных, которые могут измениться - это нарушает принцип чистоты
  • Путаница с useCallback или useMemo - это разные механизмы для разных целей
  • Забывание, что функция вызывается только при монтировании, и попытка обновить состояние через неё при ререндерах

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью