Я использую реагирующие перехватчики 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 не может вспомнить tbh, но, похоже, много хороших ответов ниже.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


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/…
@peterflanagan видео про яйцеголовых (у меня тоже нет доступа) описывает реализацию ловушки useEffect с глубоким сравнением. Я обновил свой ответ подсказкой, надеюсь, это поможет, но вам нужно реализовать алгоритм сравнения, который наиболее подходит для вас, используя lodash, или что-то еще должно сделать эту работу, но если вы не можете использовать сторонние библиотеки, вы можете реализовать собственное.
@peterflanagan может быть лучше просто переключиться на компонент на основе классов с didComponentUpdate? JSON.stringify может свести на нет любое повышение производительности, в то время как сложное сравнение может свести на нет лучшую читаемость функциональных компонентов.
@lenilsondc для альтернативного случая важно передать массив зависимостям useEffect - если вы передадите объект, он не будет работать должным образом.
@lenilsondc В альтернативном методе в useEffect не передается массив. Вы можете объяснить, как это работает? Когда я попытался использовать этот подход, ESLint пожаловался, что я передаю функцию в качестве второго аргумента для useEffect вместо массива.
@Alex Я внес несколько изменений, он работает так, как вы ожидаете, или есть какие-то проблемы с этим кодом? Какое-то время со мной было так обнажено: D
Я только что нашел решение, которое мне подходит.
Вы должны использовать 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?
Если вы действительно уверены, что не можете управлять apiOptions, просто замените собственный useEffect на https://github.com/kentcdodds/use-deep-compare-effect.
Это правильный ответ. useDeepCompareEffect также разработан главным инженером библиотеки тестирования React. Далее, код в useDeepCompareEffect очень маленький и простой для понимания. Я настоятельно рекомендую прочитать это.
Если ввод достаточно мелкий, и вы думаете, что глубокое равенство все равно будет быстрым, рассмотрите возможность использования 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, если вы используете apiOptions из родительской области, вам придется добавить его как зависимость к useEffect, что вернет проблему.
Вы можете использовать useDeepCompareEffect, useCustomCompareEffect или написать свой собственный хук.
https://github.com/kentcdodds/use-deep-compare-effecthttps://github.com/sanjagh/use-custom-compare-effect
Добро пожаловать в SO! мы очень ценим вашу помощь и ответы на вопросы. Может быть, лучше подробнее объяснить, что находится в ссылке, и просто использовать ее как ссылку.
Предположим, что большая часть вашего apiOptions статична и не меняется при повторном рендеринге компонента, я предлагаю следующий подход:
const useExample = (apiOptions, deps) => {
useEffect(callback, deps);
return { ... };
};
Идея состоит в том, чтобы передать в качестве второго параметра только массив значений, от которых зависит эффект.
Было ли решение для этого? Вы не выбрали ответ, поэтому мне интересно, вы просто пошли другим путем?