Реагировать 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 { ... };
};
Идея состоит в том, чтобы передать в качестве второго параметра только массив значений, от которых зависит эффект.