Реагировать 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.log(objA === objB)

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

8 ответов

использование 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.log('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, useDeepCompareMemoize(dependencies))
}

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

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

Вы должны использовать usePrevious() а также _.isEqual() из Лодаша. Внутри useEffect()ставишь условие если предыдущий apiOptions равно текущему apiOptions, Если правда, ничего не делай. Если false 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;
};

Если ввод достаточно мелкий, и вы думаете, что глубокое равенство все равно будет быстрым, рассмотрите возможность использования 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затем просто замените собственный useEffect на https://github.com/kentcdodds/use-deep-compare-effect.

В некоторых случаях это действительно так просто !

      const objA = {
   method: 'GET'
}

const objB = {
   method: 'GET'
}

console.log(objA === objB) // false

Почему objA не равно objB ? Coz JS просто сравнивает их адреса, верно? Это два разных объекта. Это мы все знаем!

То же самое, что и хуки React!

Итак, как мы все знаем, objA.method === objB.method, верно? Потому что они буквальные.

Выходит ответ:

      React.useEffect(() => {
    // do your facy work
}, [obj.method])

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

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

Еще один вариант, если у вас есть возможность изменить doSomethingCool:

Если точно знать, какие не- Objectсвойства, которые вам нужны, вы можете ограничить список зависимостей свойствами, которые useEffectбудет правильно интерпретировать с ===, например:

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

    return {
        data
    };
};

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

      const useExample = (apiOptions, deps) => {

  useEffect(callback, deps);

  return { ... };
};

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

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