1. useMount
Раньше я часто писала код в таком стиле (см. ниже), и когда компонент впервые рендерился, мне нужно было отправить запрос или выполнить какую-то другую логику.
useEffect(() => {
// запрос fetch...
}, [])
Довольно просто, но есть один большой недостаток: семантика не достаточно ясна, даже если передается пустой массив.
Поэтому следует настроить хук под названием useMount
. Тогда функция обратного вызова будет выполняться только при первом рендеринге компонента.
Исходный код
const useMount = (callback) => {
React.useEffect(callback, [])
}
Пример
const UseMountDemo = () => {
const [count, setCount] = React.useState(0)
useMount(() => {
console.log("useMount")
})
return (
<div>
count: { count }
<button onClick={() => setCount(count++)}>add</button>
</div>
)
}
Когда компонент рендерится повторно, useMount
не выполняется снова.
2. useUnmount
Когда компонент “размонтирован” и нужно запустить некоторую логику, например очистить таймер, используется подобный код:
React.useEffect(() => {
return () => {
//Запускать, когда компонент размонтирован
}
}, [])
Очевидно, что невозможно напрямую увидеть процесс выполнения, когда компонент выгружен.
Можно разработать хук useUnmount
, чтобы указать, что функция обратного вызова выполняется, когда компонент размонтирован.
Исходный код
Действительно ли все так просто? Посмотрим, как правильно использовать этот хук.
const useUnmount = (callback) => {
React.useEffect(() => {
return callback
}, [])
}
Пример
const Child = () => {
const [ count, setCount ] = useState(0)
useUnmount(() => {
// Мы ожидаем вывод значения num, когда компонент будет размонтирован
console.log('useUnmount', count)
})
return (
<div>
count: {count}
<button onClick={() => setCount(count + 1)}>add</button>
</div>
)
}
const UseUnmountedDemo = () => {
const [showChild, setShowChild] = React.useState(true);
// Мы используем "showChild" для управления отображением и скрытием дочернего компонента
return (
<div>
{ !!showChild && <Child /> }
<button onClick={() => setShowChild(false)}>Destroy child</button>
</div>
)
}
Когда компонент Child
удаляется, функция обратного вызова useUnmount
выполняется, но обнаруживается, что count
— это начальное значение 0, а не 10. Что вызывает такой неправильный результат?
Причина этого кроется в механизме закрытия в useEffect
, потому что функция передается при выгрузке компонента для первого рендеринга. Чтобы получить состояние в реальном времени, нужно использовать useRef
.
const useUnmount = (callback) => { const callbackRef = React.useRef(callback) callbackRef.current = callback React.useEffect(() => { return () => { callbackRef.current() } }, []) }
Теперь при удалении компонента получаем правильное значение count
.
3. useUpdateEffect
Иногда необходимо выполнять логический код только после изменения зависимости. Можно ли мы достичь этой цели, используя такой подход?
function UseUpdateEffectDemo() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log('count changed', count)
}, [ count ])
return (
<div>
count: {count}
<button onClick={() => setCount(count + 1)}>change</button>
</div>
)
}
Однако count
выведет 0
, как только компонент будет смонтирован. Как можно выполнить функцию обратного вызова после изменения count
?
const useUpdateEffect = function (effectCallback, deps = []) {
const isFirstMount = React.useRef(false)
React.useEffect(() => {
return () => {
isFirstMount.current = false
}
}, [])
React.useEffect(() => {
// Не выполняйте effectcallback для первого раза
if (!isFirstMount.current) {
isFirstMount.current = true
} else {
return effectCallback()
}
}, deps)
}
function UseUpdateEffectDemo() {
const [count, setCount] = React.useState(0)
useUpdateEffect(() => {
console.log('Count changed', count)
}, [ count ])
return (
<div>
count: {count}
<button onClick={() => setCount(count + 1)}>change</button>
</div>
)
}
4. useSetState
При написании компонента class
обычно используется this.setState
для манипулирования данными компонента. Между тем setState
зачастую более удобен при работе с объектными типами данных.
const [ person, setPerson ] = React.useState({
name: 'fatfish',
age: 100
})
// Изменяем имя
setPerson({
...person,
name: 'medium'
})
// Изменяем возраст
setPerson({
...person,
age: 1000
})
// Используем setState для изменения имени
setState({
name: 'medium'
})
// Используем setState для изменения возраста
setState({
age: 1000
})
Можно ли реализовать хук useSetState
, чтобы упростить работу с setPerson
? Это очень легко сделать: просто выполните обертку вокруг useState
.
const useSetState = (initState) => {
const [ state, setState ] = React.useState(initState)
const setMergeState = (value) => {
setState((prevValue) => {
const newValue = typeof value === 'function' ? value(prevValue) : value
return newValue ? { ...prevValue, ...newValue } : prevValue
})
}
return [ state, setMergeState ]
}
Пример
function useSetStateDemo() {
const [ person, setPerson ] = useSetState({
name: 'fatfish',
age: 100
})
// Изменение значения person в обычном режиме настройки
const onSetName = () => {
setPerson({ name: 'medium' })
}
// Используем функцию обратного вызова для изменения значения person
const onSetAge = () => {
setPerson(() => {
return {
age: 1000
}
})
}
return (
<div>
<p>name: { person.name }</p>
<p>age: { person.age }</p>
<button onClick={onSetName}>change name</button>
<button onClick={onSetAge}>change age</button>
</div>
)
}
Читайте также:
- 7 методов оптимизации производительности React
- Веб-разработка: основы статического сайта
- Как уменьшить размер компонента React: 3 профессиональных приема
Читайте нас в Telegram, VK и Дзен
Перевод статьи fatfish: 4 Custom React Hooks Every Developer Should Know