Is er een systematische aanpak om fouten op te sporen waardoor een component opnieuw wordt weergegeven in React? Ik heb een eenvoudige console.log() geplaatst om te zien hoe vaak het wordt weergegeven, maar ik heb problemen om erachter te komen waardoor de component meerdere keren wordt weergegeven, dat wil zeggen (4 keer) in mijn geval. Bestaat er een tool die een tijdlijn en/of alle componentenboomweergaven en -volgorde toont?
Antwoord 1, autoriteit 100%
Als je een kort fragment wilt zonder externe afhankelijkheden, vind ik dit handig
componentDidUpdate(prevProps, prevState) {
Object.entries(this.props).forEach(([key, val]) =>
prevProps[key] !== val && console.log(`Prop '${key}' changed`)
);
if (this.state) {
Object.entries(this.state).forEach(([key, val]) =>
prevState[key] !== val && console.log(`State '${key}' changed`)
);
}
}
Hier is een kleine haak die ik gebruik om updates van functiecomponenten te traceren
function useTraceUpdate(props) {
const prev = useRef(props);
useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v];
}
return ps;
}, {});
if (Object.keys(changedProps).length > 0) {
console.log('Changed props:', changedProps);
}
prev.current = props;
});
}
// Usage
function MyComponent(props) {
useTraceUpdate(props);
return <div>{props.children}</div>;
}
Antwoord 2, autoriteit 23%
Je kunt de reden voor de (re)render van een component controleren met de React Devtools profiler-tool. Geen wijziging van code nodig. Zie de blogpost van het reactieteam Introductie van de React Profiler.
Ga eerst naar instellingen tandwiel > profiler, en selecteer “Opnemen waarom elke component wordt weergegeven”
Antwoord 3, autoriteit 21%
Hier zijn enkele gevallen waarin een React-component opnieuw wordt weergegeven.
- Renderer van bovenliggende component
- Aanroepen van
this.setState()
binnen de component. Dit activeert de volgende levenscyclusmethoden van componentenshouldComponentUpdate
>componentWillUpdate
>render
>componentDidUpdate
- Wijzigingen in de
props
van de component. Dit activeertcomponentWillReceiveProps
>shouldComponentUpdate
>componentWillUpdate
>render
>componentDidUpdate
(connect
methode vanreact-redux
activeert dit wanneer er wijzigingen zijn in de Redux-store) - aanroepen van
this.forceUpdate
wat vergelijkbaar is metthis.setState
Je kunt het opnieuw renderen van je component minimaliseren door een controle in je shouldComponentUpdate
te implementeren en false
te retourneren als dat niet nodig is.
Een andere manier is om React.PureComponent
of stateless componenten te gebruiken. Pure en stateless componenten worden alleen opnieuw weergegeven als er wijzigingen zijn in de rekwisieten.
Antwoord 4, autoriteit 3%
@jpdelatorre’s antwoord is geweldig om algemene redenen te benadrukken waarom een React-component opnieuw zou kunnen worden weergegeven.
Ik wilde alleen wat dieper in één geval duiken: wanneer rekwisieten veranderen. Problemen oplossen waardoor een React-component opnieuw wordt weergegeven, is een veelvoorkomend probleem, en in mijn ervaring het opsporen van dit probleem houdt vaak in dat wordt bepaald welke rekwisieten veranderen.
Reageercomponenten worden opnieuw weergegeven wanneer ze nieuwe rekwisieten ontvangen. Ze kunnen nieuwe rekwisieten ontvangen zoals:
<MyComponent prop1={currentPosition} prop2={myVariable} />
of als MyComponent
is verbonden met een redux-winkel:
function mapStateToProps (state) {
return {
prop3: state.data.get('savedName'),
prop4: state.data.get('userCount')
}
}
Elke keer dat de waarde van prop1
, prop2
, prop3
of prop4
verandert MyComponent
wordt opnieuw weergegeven. Met 4 props is het niet zo moeilijk om op te sporen welke props veranderen door een console.log(this.props)
aan het begin van het render
blok te plaatsen. Met meer gecompliceerde componenten en steeds meer rekwisieten is deze methode echter onhoudbaar.
Hier is een handige benadering (met behulp van lodashvoor het gemak) om te bepalen welke propwijzigingen ervoor zorgen dat een component opnieuw wordt render:
componentWillReceiveProps (nextProps) {
const changedProps = _.reduce(this.props, function (result, value, key) {
return _.isEqual(value, nextProps[key])
? result
: result.concat(key)
}, [])
console.log('changedProps: ', changedProps)
}
Door dit fragment aan uw component toe te voegen, kan de boosdoener worden opgespoord die dubieuze re-renders veroorzaakt, en vaak helpt dit licht te werpen op onnodige gegevens die in componenten worden doorgesluisd.
Antwoord 5, autoriteit 2%
Vreemd dat niemand dat antwoord heeft gegeven, maar ik vind het erg handig, vooral omdat de rekwisietenwisselingen bijna altijd diep genest zijn.
Haken fanboys:
import deep_diff from "deep-diff";
const withPropsChecker = WrappedComponent => {
return props => {
const prevProps = useRef(props);
useEffect(() => {
const diff = deep_diff.diff(prevProps.current, props);
if (diff) {
console.log(diff);
}
prevProps.current = props;
});
return <WrappedComponent {...props} />;
};
};
“Oude”-schoolfanboys:
import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
const diff = deep_diff.diff(prevProps, this.props);
if (diff) {
console.log(diff);
}
}
P.S. Ik gebruik nog steeds liever HOC (component van hogere orde) omdat je soms je rekwisieten aan de bovenkant hebt gedestructureerd en de oplossing van Jacob niet goed past
Disclaimer: geen enkele band met de pakketeigenaar. Gewoon tientallen keren klikken om te proberen het verschil in diep geneste objecten te zien, is lastig.
Antwoord 6
Het gebruik van haken en functionele componenten, niet alleen het wisselen van props, kan een rerender veroorzaken. Wat ik begon te gebruiken, is een nogal handmatig logboek. Het heeft me erg geholpen. Misschien vind je het ook handig.
Ik kopieer dit deel in het bestand van de component:
const keys = {};
const checkDep = (map, key, ref, extra) => {
if (keys[key] === undefined) {
keys[key] = {key: key};
return;
}
const stored = map.current.get(keys[key]);
if (stored === undefined) {
map.current.set(keys[key], ref);
} else if (ref !== stored) {
console.log(
'Ref ' + keys[key].key + ' changed',
extra ?? '',
JSON.stringify({stored}).substring(0, 45),
JSON.stringify({now: ref}).substring(0, 45),
);
map.current.set(keys[key], ref);
}
};
Aan het begin van de methode bewaar ik een WeakMap-referentie:
const refs = useRef(new WeakMap());
Na elke “verdachte” oproep (rekwisieten, haken) schrijf ik:
const example = useExampleHook();
checkDep(refs, 'example ', example);
Antwoord 7
Dankzij https://stackoverflow.com/a/51082563/2391795antwoord heb ik deze iets andere oplossing voor alleen functionele componenten (TypeScript), die ook statussen afhandelt en niet alleen rekwisieten.
import {
useEffect,
useRef,
} from 'react';
/**
* Helps tracking the props changes made in a react functional component.
*
* Prints the name of the properties/states variables causing a render (or re-render).
* For debugging purposes only.
*
* @usage You can simply track the props of the components like this:
* useRenderingTrace('MyComponent', props);
*
* @usage You can also track additional state like this:
* const [someState] = useState(null);
* useRenderingTrace('MyComponent', { ...props, someState });
*
* @param componentName Name of the component to display
* @param propsAndStates
* @param level
*
* @see https://stackoverflow.com/a/51082563/2391795
*/
const useRenderingTrace = (componentName: string, propsAndStates: any, level: 'debug' | 'info' | 'log' = 'debug') => {
const prev = useRef(propsAndStates);
useEffect(() => {
const changedProps: { [key: string]: { old: any, new: any } } = Object.entries(propsAndStates).reduce((property: any, [key, value]: [string, any]) => {
if (prev.current[key] !== value) {
property[key] = {
old: prev.current[key],
new: value,
};
}
return property;
}, {});
if (Object.keys(changedProps).length > 0) {
console[level](`[${componentName}] Changed props:`, changedProps);
}
prev.current = propsAndStates;
});
};
export default useRenderingTrace;
Merk op dat de implementatie zelf niet veel is veranderd. De documentatie laat zien hoe je het voor beide rekwisieten/staten kunt gebruiken en het onderdeel is nu geschreven in TypeScript.
Antwoord 8
De bovenstaande antwoorden zijn erg nuttig, voor het geval iemand op zoek is naar een specifieke methode om de oorzaak van opnieuw renderen te detecteren, dan vond ik deze bibliotheek redux-loggererg nuttig.
Wat u kunt doen is de bibliotheek toevoegen en differentiëren tussen staat inschakelen (het staat in de documenten) zoals:
const logger = createLogger({
diff: true,
});
En voeg de middleware toe in de winkel.
Plaats vervolgens een console.log()
in de renderfunctie van de component die u wilt testen.
Vervolgens kunt u uw app uitvoeren en controleren op consolelogboeken. Waar er een logboek is net ervoor, wordt het verschil tussen de staat (nextProps and this.props)
weergegeven en kunt u beslissen of renderen is echt nodig daar
Het lijkt op bovenstaande afbeelding samen met de diff-toets.