На этой неделе я работал рядом с коллегой и услышал, как они ругаются на свой экран. В полезный момент я подошел и не удивился, обнаружив, что проблема связана с крючками.
В этом случае разработчик боролся с хуком useEffect, в частности, по их словам, когда «чертова вещь не срабатывает! ». Это была та же проблема, с которой я столкнулся, когда впервые играл с хуками, и поэтому это вдохновило меня написать сообщение в блоге.
Давайте кратко (потому что это уже было сделано ранее) обсудим, как работает useEffect:
useEffect(() => {
// do something funky
});
Используя этот хук в вашем функциональном компоненте, все, что вы ему скажете, будет выполняться каждый раз при рендеринге компонента.
useEffect(() => {
// do something funky only on first render
}, []);
Вы также можете добавить необязательный массив зависимостей к функции useEffect. Как и в приведенном выше примере, это может быть пустой массив, указывающий, что вы хотите, чтобы эффект запускался только при начальной визуализации. Идеально подходит для чего-то вроде начальной загрузки данных.
useEffect(() => {
// do something funky only when the count updates
}, [count]);
Наконец, вы можете передать переменную в массив зависимостей. Это заставляет useEffect отслеживать значение переменной и обновлять только при его изменении. На практике это вариант, который я использую чаще всего, поскольку обычно хочу выполнить действие, когда что-то в моем приложении изменяется.
Именно с этим примером боролся мой коллега. Возьмите этот упрощенный пример компонента:
import React, {useState, useEffect} from 'react';
const App = () => {
const [name, setName] = useState('');
const [array, setArray] = useState([{'name': 'Jon', 'age': 20}, {'name': 'Jill', 'age': 40}]);
useEffect(() => {
console.log(name);
console.log('name updated');
}, [name]);
useEffect(() => {
console.log(array);
console.log('array updated');
}, [array]);
const changeName = () => {
setName('Dave');
}
const changeArray = () => {
array[1] = {'name': 'Jill', 'age': 60};
setArray(array);
}
return (
<div>
<button onClick={() => changeName()}>
Change Name
</button>
<button onClick={() => changeArray()}>
Change Array
</button>
</div>
)
}
export default App;
Здесь у нас есть функциональная составляющая. Он имеет две переменные состояния, использующие ловушку setState: имя - строка (тип значения) и массив - массив (ссылочный тип).
const [name, setName] = useState('');
const [array, setArray] = useState([
{'name': 'Jon', 'age': 20},
{'name': 'Jill', 'age': 40}
]);
Он также имеет два перехватчика useEffect, один из которых запускается при изменении имени, а другой - при изменении массива:
useEffect(() => {
console.log(name);
console.log('name updated');
}, [name]);
useEffect(() => {
console.log(array);
console.log('array updated');
}, [array]);
При срабатывании эти хуки просто отключают отслеживаемое значение и выводят сообщение об обновлении.
const changeName = () => {
setName('Dave');
}
const changeArray = () => {
array[1] = {'name': 'Jill', 'age': 60};
setArray(array);
}
return (
<div>
<button onClick={() => changeName()}>
Change Name
</button>
<button onClick={() => changeArray()}>
Change Array
</button>
</div>
)
}
Наконец, у нас есть две функции, назначенные на нажатие двух кнопок, что позволяет нам вручную обновлять имя и массив.
Если мы нажмем первую кнопку, мы увидим в консоли следующее:
>Dave >name updated
Все в порядке.
Но если мы нажмем вторую кнопку, консоль останется пустой. Хук не сработал.
Означает ли это, что обновление нашего массива не удалось? Мы можем изменить первый хук, чтобы показать нам:
useEffect(() => {
console.log(name);
console.log(array);
console.log('name updated');
}, [name]);
Итак, теперь мы нажимаем вторую кнопку, чтобы обновить массив, затем первую и консоль показывает это:
>Dave
>(2) [{…}, {…}]
>0: {name: “Jon”, age: 20}
>1: {name: “Jill”, age: 60}
>__proto__
>name updated
Мы видим, что возраст Джилл обновлен. Так почему же не срабатывает перехватчик useEffect?
Проблема в том, что ловушка useEffect не выполняет сравнение объектов или массивов, а просто проверяет, изменяется ли ссылка или нет. Поскольку объекты и массивы являются ссылочными типами, ловушка не срабатывает при изменении одного из их значений.
Решение очень простое. Нам нужно изменить наш вызов setArray с:
setArray(array);
to:
setArray([...array]);
Если установить для массива старое значение, распространить на новый массив, ссылка изменится на новую версию и сработает перехватчик.
Вот и все! Не могу сказать, сколько часов я потерял из-за того первого раза!
Существует также другое решение, позволяющее проводить полное сравнение изменений значений в объекте. Я наткнулся на это, исследуя эту проблему, в другой средней статье, в которой описывалась аналогичная проблема и предлагался специальный крючок, чтобы помочь - очень приятно! Взгляните на это здесь:
Все сделано
Итак, у вас есть одна причина меньше кричать на свой экран. Найди другого: D