Traceer waarom een ​​React-component opnieuw wordt weergegeven

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”

React Dev Tools >Instellingen”></a></p>
<p><a href=Screenshot van React Devtools profiler


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 componenten shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate
  • Wijzigingen in de propsvan de component. Dit activeert componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate(connectmethode van react-reduxactiveert dit wanneer er wijzigingen zijn in de Redux-store)
  • aanroepen van this.forceUpdatewat vergelijkbaar is met this.setState

Je kunt het opnieuw renderen van je component minimaliseren door een controle in je shouldComponentUpdatete implementeren en falsete retourneren als dat niet nodig is.

Een andere manier is om React.PureComponentof 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 MyComponentis 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, prop3of prop4verandert MyComponentwordt 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 renderblok 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 daarvoer hier de afbeeldingsbeschrijving in

Het lijkt op bovenstaande afbeelding samen met de diff-toets.

Other episodes