Початкове значення useState використовується лише при першому рендері. Але вираз, переданий аргументом, обчислюється на кожному рендері - бо це звичайний виклик функції компонента.
function Editor() {
// parseDraft викликається на КОЖНОМУ рендері, хоча результат потрібен один раз
const [draft, setDraft] = useState(parseDraft(localStorage.getItem('draft')));
// ...
}
Якщо обчислення дороге (розбір великого JSON, читання localStorage, побудова структури з тисяч елементів), компонент сповільнюється на кожне введення в поле.
Лінива ініціалізація - передати функцію, а не її результат:
const [draft, setDraft] = useState(() => parseDraft(localStorage.getItem('draft')));
React викличе функцію лише при першому рендері. Зверніть увагу на різницю:
useState(createInitialTodos()); // викликається щоразу
useState(createInitialTodos); // передано саму функцію - лише раз
useState(() => createInitialTodos(userId)); // з аргументом - через обгортку
Те саме для useReducer - третій аргумент-ініціалізатор:
const [state, dispatch] = useReducer(reducer, userId, createInitialState);
Для useRef вбудованої лінивої ініціалізації немає - роблять вручну:
const playerRef = useRef<VideoPlayer | null>(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer(); // створюється один раз
}
Що варто знати:
- у StrictMode функція-ініціалізатор у режимі розробки викликається двічі - вона має бути чистою (без запитів, підписок, побічних ефектів);
- для дешевих значень (
useState(0),useState([]),useState(props.initial)) лінива ініціалізація не потрібна - виграшу немає; - ініціалізація з props відбувається один раз:
useState(props.value)не оновиться, колиprops.valueзміниться. Якщо значення має стежити за props, стан не потрібен - обчислюйте під час рендеру, а для «скидання» стану використайтеkey.
Докладніше в документації: useState: уникнення повторного створення початкового стану