На этой неделе я работал рядом с коллегой и услышал, как они ругаются на свой экран. В полезный момент я подошел и не удивился, обнаружив, что проблема связана с крючками.

В этом случае разработчик боролся с хуком 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