React предоставляет программисту прекрасный базовый набор хуков и с каждой версией их количество и функционал увеличивается.
Трудно представить код современного React-приложения без таких функций как useState, useEffect, useRef и так далее.
Однако, в повседневной жизни мы часто решаем рутинные задачи, многие из которых могут быть автоматизированы.
Создание кастом-хуков это прекрасная возможность выделить часто переиспользуемый код в отдельные сущности.
Это помогает содержать основной код компонента в чистоте и избавляет нас от мелких ошибок, которые могут остаться незамеченными, когда вы пишете один и тот же код заново.
Ниже мы рассмотрим примеры некоторых из них.
1. useToggle
Приходилось ли вам когда-нибудь создавать useState, который содержал в себе только два значения true и false и назывался как-то вроде isActive, isChecked или isOpen?
Если ответ да - то вы определенно попали по адресу! Первый хук, который мы рассмотрим, инкапсулирует в себе эту логику, возвращая значение и методы для изменения его состояния.
import { useCallback, useState } from 'react' import type { Dispatch, SetStateAction } from 'react' export function useToggle( defaultValue?: boolean, ): [boolean, () => void, Dispatch<SetStateAction<boolean>>] { const [value, setValue] = useState(!!defaultValue) const toggle = useCallback(() => { setValue((x) => !x) }, []) return [value, toggle, setValue] }
Его можно легко расширить функциями, которые будут явно устанавливать значение состояния в
trueилиfalse.
Рассмотрим пример использования:
export function Component() { const [value, toggle, setValue] = useToggle() return ( <> <button onClick={toggle}>toggle</button> <button onClick={() => setValue(false)}>hide</button> {value && <div>Hello!</div>} </> ) }
2. useHover
Случались ли у вас такое, что css :hover по каким-либо причинам использовать было невозможно и ничего не оставалось, кроме как сымитировать это поведение с помощью mouseEnter и mouseLeave?
Если ответ снова положительный - то я готов вам представить второй кастом-хук, который сделает это за вас.
import { useRef, useState, useEffect } from 'react' import type { RefObject } from 'react' export function useHover<T extends HTMLElement = HTMLElement>(): [ RefObject<T>, boolean, ] { const ref = useRef<T>(null) const [isHovered, setIsHovered] = useState(false) useEffect(() => { const element = ref.current if (!element) return const handleMouseEnter = () => setIsHovered(true) const handleMouseLeave = () => setIsHovered(false) element.addEventListener('mouseenter', handleMouseEnter) element.addEventListener('mouseleave', handleMouseLeave) return () => { element.removeEventListener('mouseenter', handleMouseEnter) element.removeEventListener('mouseleave', handleMouseLeave) } }, []) return [ref, isHovered] }
Использование этого хука несколько нестандартное, давайте рассмотрим на примере:
export function Component() { const [hoverRef, isHovered] = useHover<HTMLDivElement>() return ( <div ref={hoverRef} style={{ backgroundColor: isHovered ? 'lightblue' : 'lightgray' }} > {isHovered ? 'hovered' : 'not hovered'} </div> ) }
3. useDerivedState
Порой, в компоненте мы создаем useState, начальным значением которого является какое-либо значение из пропсов.
Если пропсы изменятся, то соответствующие изменения не затронут наше локальное состояние и оно продолжит хранить устаревшее значение.
Чтобы этого избежать мы можем воспользоваться следующим хуком:
export function useDerivedState<T>( propValue: T, ): [T, Dispatch<SetStateAction<T>>] { const [state, setState] = useState(propValue) useEffect(() => { setState(propValue) }, [propValue]) return [state, setState] }
Это может быть полезно в случаях с пользовательским вводом, когда мы хотим изменить значение и только затем его сохранить или вернуть изначальное значение.
export function Component({ initialName }: { initialName: string }) { const [name, setName] = useDerivedState(initialName) return ( <> <input value={name} onChange={(e) => setName(e.target.value)} /> <div>Current name: {name}</div> </> ) }
4. useEventCallback
Все мы привыкли пользоваться хуком useCallback, который кеширует функцию между ре-рендерами.
Однако, если в массиве зависимостей этой функции будут значения, которые изменились - функция будет создана заново.
С точки зрения оптимизации производительности это может быть излишним, так как ваш коллбэк мог так ни разу и не быть вызванным.
Если вы хотите получить стабильную ссылку на коллбэк, который не меняется от рендера к рендеру, но при этом в момент вызова всегда содержит актуальные значения переменных, от которых он зависит, то вы можете воспользоваться следующим хуком:
export function useEventCallback<I extends unknown[], O>( fn: (...args: I) => O, ): (...args: I) => O { const ref = useRef<(...args: I) => O>() useLayoutEffect(() => { ref.current = fn }, [fn]) return useCallback((...args) => { const { current } = ref if (current == null) { throw new Error( 'callback created in useEventCallback can only be called from event handlers', ) } return current(...args) }, []) }
Чаще всего этот хук используется для коллбэков, вызов которых отложен во времени и инициируется пользователем. Удачным примером будет замена им обычных коллбэков для передачи в onClick:
export function Component() { const [count, setCount] = useState(0) const increment = useEventCallback(() => { setCount((prev) => prev + 1) }) return ( <div> <p>{count}</p> <button onClick={increment}>Add</button> </div> ) }
5. useDebouncedCallback
При взаимодействии пользователя с интерйфесом через такие события как: ввод текста, изменение ширины окна браузера, скролл - может возникать чрезмерно большое количество вызовов функций-коллбэков.
Зачастую нам это не нужно и мы хотим отложить вызов до момента, когда пользователь закончит действие, чтобы затем выполнить полезный код.
import { useEffect, useMemo, useRef } from 'react' import debounce from 'lodash.debounce' export function useDebouncedCallback<T extends (...args: any[]) => any>( func: T, delay = 500, ) { const funcRef = useRef(func) useEffect(() => { funcRef.current = func }, [func]) const debounced = useMemo(() => { const debouncedFn = debounce( (...args: Parameters<T>) => funcRef.current(...args), delay, ) return debouncedFn }, [delay]) useEffect(() => { return () => { debounced.cancel() } }, [debounced]) return debounced }
Этот хук можно расширить такими вспомогательными функциями как
cancel,isPendingиflush.
Рассмотрим пример использования:
export function Component() { const [value, setValue] = useState('') const debouncedSearch = useDebouncedCallback((query: string) => { console.log('Search by:', query) }, 500) const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value setValue(newValue) debouncedSearch(newValue) } return ( <input type="text" placeholder="Search..." value={value} onChange={handleChange} /> ) }
Вот и все! Количество и функционал кастом-хуков может быть самым разнообразным, все ограничено лишь вашей фантазией и потребностями.
За большим количеством примеров вы можете обратиться в такие библиотеки как react-use или usehooks-ts, а также многие другие.
ссылка на оригинал статьи https://habr.com/ru/articles/880570/
Добавить комментарий