Реагировать useEffect сравнение объектов

Я использую реагирующие перехватчики useEffect и проверяю, изменился ли объект, и только затем снова запускаю перехватчик.

Мой код выглядит так.

const useExample = (apiOptions) => {
    const [data, updateData] = useState([]);
    useEffect(() => {
       const [data, updateData] = useState<any>([]);
        doSomethingCool(apiOptions).then(res => {               
           updateData(response.data);
       })
    }, [apiOptions]);

    return {
        data
    };
};

К сожалению, он продолжает работать, поскольку объекты не распознаются как одинаковые.

Я считаю, что следующее является примером того, почему.

const objA = {
   method: 'GET'
}

const objB = {
   method: 'GET'
}

console.info(objA === objB)

Возможно, работает JSON.stringify(apiOptions)?

Было ли решение для этого? Вы не выбрали ответ, поэтому мне интересно, вы просто пошли другим путем?

FabricioG 29.08.2020 02:10

@FabricioG не может вспомнить tbh, но, похоже, много хороших ответов ниже.

peter flanagan 06.02.2021 15:23
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
101
2
93 564
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Использовать apiOptions как значение состояния

Я не уверен, как вы используете пользовательский хук, но создание apiOptions как значения состояния с помощью useState должно работать нормально. Таким образом, вы можете передать его своему пользовательскому хуку как значение состояния, например:

const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)

Таким образом, это изменится только при использовании setApiOptions.

Пример # 1

import { useState, useEffect } from 'react';

const useExample = (apiOptions) => {
  const [data, updateData] = useState([]);
  
  useEffect(() => {
    console.info('effect triggered')
  }, [apiOptions]);

  return {
    data
  };
}
export default function App() {
  const [apiOptions, setApiOptions] = useState({ a: 1 })
  const { data } = useExample(apiOptions);
  const [somethingElse, setSomethingElse] = useState('default state')

  return <div>
    <button onClick = {() => { setApiOptions({ a: 1 }) }}>change apiOptions</button>
    <button onClick = {() => { setSomethingElse('state') }}>
      change something else to force rerender
    </button>
  </div>;
}

Альтернативно

Вы можете написать глубоко сопоставимый useEffect, как описано здесь:

function deepCompareEquals(a, b){
  // TODO: implement deep comparison here
  // something like lodash
  // return _.isEqual(a, b);
}

function useDeepCompareMemoize(value) {
  const ref = useRef() 
  // it can be done by using useMemo as well
  // but useRef is rather cleaner and easier

  if (!deepCompareEquals(value, ref.current)) {
    ref.current = value
  }

  return ref.current
}

function useDeepCompareEffect(callback, dependencies) {
  useEffect(
    callback,
    dependencies.map(useDeepCompareMemoize)
  )
}

Вы можете использовать его так же, как и useEffect.

Спасибо за вашу помощь, но я не хочу использовать этот подход, поскольку может быть много вариантов API. Я решил эту проблему с помощью JSON.stringify, но похоже, что есть лучший способ сделать это, описанный здесь, но у меня нет доступа к яйцеголовой :-( stackoverflow.com/questions/53601931/…

peter flanagan 08.01.2019 18:02

@peterflanagan видео про яйцеголовых (у меня тоже нет доступа) описывает реализацию ловушки useEffect с глубоким сравнением. Я обновил свой ответ подсказкой, надеюсь, это поможет, но вам нужно реализовать алгоритм сравнения, который наиболее подходит для вас, используя lodash, или что-то еще должно сделать эту работу, но если вы не можете использовать сторонние библиотеки, вы можете реализовать собственное.

lenilsondc 08.01.2019 18:52

@peterflanagan может быть лучше просто переключиться на компонент на основе классов с didComponentUpdate? JSON.stringify может свести на нет любое повышение производительности, в то время как сложное сравнение может свести на нет лучшую читаемость функциональных компонентов.

skyboyer 12.01.2019 20:58

@lenilsondc для альтернативного случая важно передать массив зависимостям useEffect - если вы передадите объект, он не будет работать должным образом.

Krzysztof Cieslinski 09.05.2020 13:30

@lenilsondc В альтернативном методе в useEffect не передается массив. Вы можете объяснить, как это работает? Когда я попытался использовать этот подход, ESLint пожаловался, что я передаю функцию в качестве второго аргумента для useEffect вместо массива.

Alex 22.10.2020 17:12

@Alex Я внес несколько изменений, он работает так, как вы ожидаете, или есть какие-то проблемы с этим кодом? Какое-то время со мной было так обнажено: D

lenilsondc 22.10.2020 23:25

Я только что нашел решение, которое мне подходит.

Вы должны использовать usePrevious() и _.isEqual() от Lodash. Внутри useEffect() вы устанавливаете условие, если предыдущийapiOptions равно ТекущийapiOptions. Если это правда, ничего не делать. Если ложно updateData ().

Пример :

const useExample = (apiOptions) => {

     const myPreviousState = usePrevious(apiOptions);
     const [data, updateData] = useState([]);
     useEffect(() => {
        if (myPreviousState && !_.isEqual(myPreviousState, apiOptions)) {
          updateData(apiOptions);
        }
     }, [apiOptions])
}

usePrevious(value) - это настраиваемая ловушка, которая создает ref с useRef().

Вы можете найти его в Официальная документация React Hook.

const usePrevious = value => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

Появляется ли предупреждение об отсутствии зависимости myPreviousState?

Up209d 12.06.2020 04:50

Если вы действительно уверены, что не можете управлять apiOptions, просто замените собственный useEffect на https://github.com/kentcdodds/use-deep-compare-effect.

Это правильный ответ. useDeepCompareEffect также разработан главным инженером библиотеки тестирования React. Далее, код в useDeepCompareEffect очень маленький и простой для понимания. Я настоятельно рекомендую прочитать это.

crazyGuy 11.08.2020 14:15

Если ввод достаточно мелкий, и вы думаете, что глубокое равенство все равно будет быстрым, рассмотрите возможность использования JSON.stringify:

const useExample = (apiOptions) => {
    const [data, updateData] = useState([]);
    const apiOptionsJsonString = JSON.stringify(apiOptions);

    useEffect(() => {
       const apiOptionsObject = JSON.parse(apiOptionsJsonString);
       doSomethingCool(apiOptionsObject).then(response => {               
           updateData(response.data);
       })
    }, [apiOptionsJsonString]);

    return {
        data
    };
};

Обратите внимание, что он не сравнивает функции.

Это кажется хорошим и простым решением, когда apiOptions не слишком сложный объект данных. Но мне интересно: а нужно ли делать JSON.parse(apiOptionsJsonString) внутри useEffect ()? Разве вы не можете просто использовать apiOptions из родительского прицела?

marcvangend 28.08.2020 10:34

@marcvangend, если вы используете apiOptions из родительской области, вам придется добавить его как зависимость к useEffect, что вернет проблему.

adl 29.08.2020 19:50

Вы можете использовать useDeepCompareEffect, useCustomCompareEffect или написать свой собственный хук.

https://github.com/kentcdodds/use-deep-compare-effecthttps://github.com/sanjagh/use-custom-compare-effect

Добро пожаловать в SO! мы очень ценим вашу помощь и ответы на вопросы. Может быть, лучше подробнее объяснить, что находится в ссылке, и просто использовать ее как ссылку.

El.Hum 19.09.2020 07:38

Предположим, что большая часть вашего apiOptions статична и не меняется при повторном рендеринге компонента, я предлагаю следующий подход:

const useExample = (apiOptions, deps) => {

  useEffect(callback, deps);

  return { ... };
};

Идея состоит в том, чтобы передать в качестве второго параметра только массив значений, от которых зависит эффект.

Другие вопросы по теме