На этой неделе я работал рядом с коллегой и услышал, как они ругаются на свой экран. В полезный момент я подошел и не удивился, обнаружив, что проблема связана с крючками.
В этом случае разработчик боролся с хуком 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