Возможно, вы не все знаете о useState...
Не ожидайте от этой статьи громкого разоблачения хука useState, но вот 7 фактов, которые стоило бы знать всем, кто собирается использовать хуки в react, я вам расскажу!
1. Функция обновления состояния имеет постоянную ссылку
Функция обновления стейта (второй элемент массива) - это одна и та же функция при каждом рендеринге. Вам не нужно включать его в зависимости массива, независимо от того, что eslint-plugin-react-hooks говорит по этому поводу:
const [count, setCount] = useState(0);
const onChange = useCallback((e) => {
// setCount никогд не менятеся
setCount(Number(e.target.value));
}, []);2. При установке одинакового состояния ничего не происходит
useState - по умолчанию является чистой функцией. Если мы меняем значение стейта на его предыдущее значение, то ничего не происходит - нет обновления DOM, нет ненужной перерисовки, ничего нет.
Именно поэтому делать эту проверку самому - избыточно!
const [isOpen, setOpen] = useState(props.initOpen);
const onClick = () => {
// useState уже произведет эту проверку за нас
if (!isOpen) {
setOpen(true);
}
};Но это работает только со значениями, которые не создают новую ссылку на себя каждый раз:
const [{ isOpen }, setState] = useState({ isOpen: true });
const onClick = () => {
// каждый раза вызывает обновление
// поскольку ссылка на объект является новой каждый раз
setState({ isOpen: false });
};3. Оптимизация инициализации стейта
Если вы переживаете за то что каждый раз будет создаваться стартовый объект инициализации, то можно использовать функцию:
const [style, setStyle] = useState(() => ({
transform: props.isOpen ? null : 'translateX(-100%)',
opacity: 0
}));Честно говоря, для меня это выглядит как чрезмерная оптимизация - зачем беспокоиться об одном объекте? Это может помочь с тяжелой логикой инициализации, но я еще не видела такого случая.С другой стороны, если вы хотите поместить функцию в свое состояние (это не запрещено, не так ли?), вы должны обернуть ее в дополнительную функцию, чтобы обойти логику ленивой инициализации:
useState(() => () => console.log('Hello, traveler!'))4. Обновление состояния при помощи колбека
Колбеки можно также использовать для того, чтобы обновлять состояние компонента - что-то вроде мини-редьюсера, у которого нет действия.
Это может быть полезно, когда текущее значение может не совпадать с итоговым значением, такое возможно если вы обновили значение с момента предыдущего рендера / мемоизации. Думаю, проще будет понять на примере:
const [clicks, setClicks] = useState(0);
const onMouseDown = () => {
// Не будет работать, т.к clicks не изменился
setClicks(clicks + 1);
setClicks(clicks + 1);
};
const onMouseUp = () => {
// А вот это будет, т.к мы прочитаем значение кликов в колбеке
setClicks(clicks + 1);
setClicks(clicks => clicks + 1);
};Вобще, создание колбеков с постоянными ссылками более практично:
const [isDown, setIsDown] = useState(false); // Плохой вариант, изменяется каждый раз при изменении isDown const onClick = useCallback(() => setIsDown(!isDown), [isDown]); // Отличный вариант, никогда не изменяется const onClick = useCallback(() => setIsDown(v => !v), []);
5. Обновление состояния возвращает undefined
Это значит, что useState можно вернуть из хуков по типу useEffect без генерации предупреждения с таким текстом:
Warning: An effect function must not return anything besides a function, which is used for clean-up.
Вот эти фрагменты кода будут работать одинаково:
const [isOpen, setOpen] = useState(props.initOpen);
useEffect(() => { setOpen(true); }, []);
useEffect(() => setOpen(true), []);6. useState это по факту useReducer
Фактически, useState реализован в коде React как useReducer, только с заранее определенным редуктором (по крайней мере, начиная с 17.0)
Да, да, я правда люблю иногда покопаться в исходниках react...
В целом, если кто-то утверждает, что у useReducer есть множество серьезных преимуществ перед useState - то человека можно смело отправить копаться в исходниках (но помните, что это жестоко)!
7. Одно обновление состояния === один рендер в асинхронном коде
В react есть функция, называемая batching (обработка всего пакетами или пакетирование, как вам будет угодно), которая заставляет несколько вызовов setState вызывать один рендеринг, но она не всегда включена.
Давайте рассмотрим пример кода:
console.log('отрисовочка');
const [clicks, setClicks] = useState(0);
const [isDown, setIsDown] = useState(false);
const onClick = () => {
setClicks(clicks + 1);
setIsDown(!isDown);
};Когда вызовется функция onClick, то количество раз, которое будет вызвана перерисовка, на самом деле зависит от того, как была вызвана эта функция:
<button onClick={onClick}>- пакетируется как обработчик событий ReactuseEffect(onClick, [])- тоже пакетируетсяsetTimeout(onClick, 10)- не пакетируется и вызовет дополнительную перерисовкуel.addEventListener('click', onClick)- тоже не пакетируется
Но это собираются изменить в React 18, прочитать про это можно тут.
А тем временем вы можете использовать unstable_batchedUpdates для вызова принудительной пакетной обработки.
Надеюсь вы узнали для себя что-то новое!
А если изучение тайных знаний react - интересная для вас тема, то обязательно подписывайтесь на телеграм канал, чтобы не пропустить новые статьи!
Читать дальше:
- Используем forwardRef в React для оптимизации
- Красивый курсор при помощи CSS и JavaScript
- Курс по three.js