Почему в React useState для initial value лучше передавать функцию, а не результат вызова функции?
Что проверяют
Понимаете ли вы, что аргумент вычисляется на каждом рендере, и различаете ли ленивую инициализацию и функцию обновления.
Как отвечать
Начальное значение используется только при первом рендере. Но выражение, переданное аргументом, вычисляется на каждом, потому что это обычный вызов функции в теле компонента.
const [state, setState] = useState(expensiveInit()); // вызывается каждый рендер
const [state, setState] = useState(() => expensiveInit()); // вызывается один разВо втором варианте React получает функцию и вызывает её только при инициализации. Это называется ленивой инициализацией.
Когда разница существенна:
- чтение из хранилища браузера и разбор JSON: синхронная операция на каждом рендере;
- тяжёлое вычисление: построение большой структуры, разбор текста;
- создание крупного объекта, который тут же выбрасывается.
Когда безразлично: примитив или короткий литерал. Оборачивать useState(0) в функцию бессмысленно.
Важно не путать с другой функцией, которая передаётся в установщик состояния:
setCount((prev) => prev + 1);Здесь функция нужна совсем для другого: чтобы получить актуальное предыдущее значение. Без неё несколько обновлений подряд в одном обработчике опираются на одно и то же захваченное значение, и вместо трёх увеличений получается одно. Обе конструкции выглядят похоже, но решают разные задачи: первая про то, когда вычислять, вторая про то, от чего считать.
Полезное дополнение: тот же приём ленивого вычисления работает и у редьюсера, где третьим аргументом можно передать функцию подготовки начального состояния.
Где спотыкаются
Читают хранилище браузера прямо в аргументе на каждом рендере.
Путают ленивую инициализацию с функцией обновления.
Обновляют состояние несколько раз подряд от захваченного значения.
Оборачивают в функцию примитивы, усложняя код без пользы.