javascript - react useEffect jämföra objekt
Jag använder React useEffect-hooks och kollar om ett objekt har ändrats och kör bara då hooken igen.
Min kod ser ut så här.
const useExample = (apiOptions) => {
const [data, updateData] = useState([]);
useEffect(() => {
const [data, updateData] = useState<any>([]);
doSomethingCool(apiOptions).then(res => {
updateData(response.data);
})
}, [apiOptions]);
return {
data
};
};
Tyvärr fortsätter det att köras eftersom objekten inte känner igen varandra som samma.
Jag tror att följande är ett exempel på varför.
const objA = {
method: 'GET'
}
const objB = {
method: 'GET'
}
console.log(objA === objB)Kanske fungerar det att köra JSON.stringify(apiOptions)?
Svar #1
Använd apiOptions som tillståndsvärde
Jag är inte säker på hur du använder den anpassade hooken, men att göra apiOptions till ett tillståndsvärde genom att använda useState borde fungera alldeles utmärkt. På så sätt kan du ge det till din anpassade hook som ett tillståndsvärde på följande sätt:
const [apiOptions, setApiOptions] = useState({ a: 1 })
const { data } = useExample(apiOptions)
På det här sättet kommer det bara att förändras när du använder setApiOptions.
Exempel #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>;
}Alternativt
Du skulle kunna skriva en djup jämförbar useEffect som beskrivs här:
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)
)
}
Du kan använda det som du skulle använda useEffect.
Svar #2
Om indata är tillräckligt grund för att du tror att djup jämförelse fortfarande skulle vara snabb, överväg att använda 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
};
};
Observera att det inte kommer att jämföra funktioner.
Svar #3
Jag hittade precis en lösning som fungerar för mig.
Du måste använda usePrevious() och _.isEqual() från Lodash. Inuti useEffect() sätter du ett villkor om de tidigare apiOptions är lika med de aktuella apiOptions. Om sant, gör ingenting. Om falskt, updateData().
Exempel :
const useExample = (apiOptions) => {
const myPreviousState = usePrevious(apiOptions);
const [data, updateData] = useState([]);
useEffect(() => {
if (myPreviousState && !_.isEqual(myPreviousState, apiOptions)) {
updateData(apiOptions);
}
}, [apiOptions])
}
usePrevious(value) är en anpassad hook som skapar en ref med useRef().
Du kan hitta det i den officiella dokumentationen för React Hook.
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
Svar #4
Om du är helt säker på att du inte kan kontrollera apiOptions så byt bara ut den inbyggda useEffect mot https://github.com/kentcdodds/use-deep-compare-effect.
