Rastree por qué un componente React se vuelve a representar

156

¿Existe un enfoque sistemático para depurar lo que está causando que un componente se vuelva a procesar en React? Puse un simple console.log () para ver cuántas veces se procesa, pero tengo problemas para descubrir qué está causando que el componente se procese varias veces, es decir (4 veces) en mi caso. ¿Existe una herramienta que muestre una línea de tiempo y / o todos los componentes del árbol y el orden?

jasan
fuente
Tal vez podría usar shouldComponentUpdatepara deshabilitar la actualización automática de componentes y luego comenzar su rastreo desde allí. Puede encontrar más información aquí: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr
La respuesta de @jpdelatorre es correcta. En general, uno de los puntos fuertes de React es que puede rastrear fácilmente el flujo de datos en la cadena mirando el código. La extensión React DevTools puede ayudar con eso. Además, tengo una lista de herramientas útiles para visualizar / rastrear la representación del componente React como parte de mi catálogo de complementos de Redux , y una serie de artículos sobre [Monitoreo del rendimiento de React] (htt
markerikson

Respuestas:

253

Si desea un fragmento corto sin dependencias externas, me parece útil

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`)
    );
  }
}

Aquí hay un pequeño gancho que uso para rastrear actualizaciones a componentes de funciones

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>;
}
Jacob Rask
fuente
55
@ yarden.refaeli No veo ninguna razón para tener un bloque if. Corto y conciso.
Isaac
Junto con esto, si encuentra que una parte del estado se está actualizando y no es obvio dónde o por qué, puede anular el setStatemétodo (en un componente de clase) con setState(...args) { super.setState(...args) }y luego establecer un punto de interrupción en su depurador que luego podrá para rastrear hasta la función que establece el estado.
redbmk
¿Cómo uso exactamente la función de enlace? ¿Dónde exactamente debo llamar useTraceUpdatedespués de haberlo definido como lo escribiste?
damon
En un componente de función, puede usarlo así function MyComponent(props) { useTraceUpdate(props); }y se registrará cada vez que cambie la utilería
Jacob Rask
1
@DawsonB probablemente no tenga ningún estado en ese componente, por this.statelo que no está definido.
Jacob Rask
67

Aquí hay algunos casos en que un componente React se volverá a representar.

  • Componente padre rerender
  • Llamando this.setState()dentro del componente. Esto dará lugar a los siguientes métodos de ciclo de vida de componentes shouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • Cambios en los componentes props. Este disparador voluntad componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectmétodo de react-reduxgatillo esto cuando hay cambios aplicables en la tienda Redux)
  • llamando this.forceUpdateque es similar athis.setState

Puede minimizar la shouldComponentUpdatedevolución de su componente implementando un cheque dentro de su y devolviendo falsesi no es necesario.

Otra forma es usar React.PureComponent componentes sin estado. Los componentes puros y sin estado solo se vuelven a renderizar cuando hay cambios en sus accesorios.

jpdelatorre
fuente
66
Nitpick: "sin estado" solo significa cualquier componente que no usa estado, ya sea que se defina con sintaxis de clase o sintaxis funcional. Además, los componentes funcionales siempre se vuelven a representar. Debe usar shouldComponentUpdate, o extender React.PureComponent, para exigir solo volver a representar en el cambio.
markerikson
1
Tienes razón sobre el componente sin estado / funcional que siempre se vuelve a representar. Actualizaré mi respuesta.
jpdelatorre
¿Puedes aclarar cuándo y por qué los componentes funcionales siempre se vuelven a representar? Utilizo bastante componentes funcionales en mi aplicación.
jasan
Entonces, incluso si usa la forma funcional de crear su componente, por ejemplo const MyComponent = (props) => <h1>Hello {props.name}</h1>;(es un componente sin estado). Se volverá a representar cada vez que se vuelva a representar el componente principal.
jpdelatorre
2
Esta es una gran respuesta segura, pero no responde la pregunta real: cómo rastrear lo que desencadenó una nueva representación. La respuesta de Jacob R parece prometedora al dar la respuesta a un problema real.
Sanuj
10

La respuesta de @ jpdelatorre es excelente para destacar las razones generales por las que un componente React podría volver a renderizarse.

Solo quería profundizar un poco más en una instancia: cuando cambian los accesorios . La resolución de problemas que está causando que un componente React se vuelva a procesar es un problema común, y en mi experiencia, muchas veces rastrear este problema implica determinar qué accesorios están cambiando .

Los componentes de reacción se renderizan cada vez que reciben nuevos accesorios. Pueden recibir nuevos accesorios como:

<MyComponent prop1={currentPosition} prop2={myVariable} />

o si MyComponentestá conectado a una tienda redux:

function mapStateToProps (state) {
  return {
    prop3: state.data.get('savedName'),
    prop4: state.data.get('userCount')
  }
}

Cada vez que el valor de prop1, prop2, prop3, o prop4cambios MyComponentserá volver a hacer. Con 4 accesorios, no es demasiado difícil rastrear qué accesorios están cambiando colocando un console.log(this.props)al comienzo del renderbloque. Sin embargo, con componentes más complicados y más y más accesorios, este método es insostenible.

Aquí hay un enfoque útil (usando lodash por conveniencia) para determinar qué cambios de utilería están causando que un componente se vuelva a renderizar:

componentWillReceiveProps (nextProps) {
  const changedProps = _.reduce(this.props, function (result, value, key) {
    return _.isEqual(value, nextProps[key])
      ? result
      : result.concat(key)
  }, [])
  console.log('changedProps: ', changedProps)
}

Agregar este fragmento a su componente puede ayudar a revelar al culpable que causa reprocesamientos cuestionables, y muchas veces esto ayuda a arrojar luz sobre los datos innecesarios que se canalizan a los componentes.

Cumulo Nimbus
fuente
3
Ahora se llama UNSAFE_componentWillReceiveProps(nextProps)y está en desuso. "Este ciclo de vida se denominó anteriormente componentWillReceiveProps. Ese nombre continuará funcionando hasta la versión 17." De la documentación de React .
Emile Bergeron
1
Puede lograr lo mismo con componentDidUpdate, que de todos modos es posiblemente mejor, ya que solo desea averiguar qué causó que un componente se actualice realmente.
ver más nítido
5

Es extraño que nadie haya dado esa respuesta, pero me parece muy útil, especialmente porque los cambios de accesorios casi siempre están profundamente anidados.

Ganchos 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} />;
  };
};

Fanboys de la "vieja escuela":

import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
      const diff = deep_diff.diff(prevProps, this.props);
      if (diff) {
        console.log(diff);
      }
}

PD: Todavía prefiero usar HOC (componente de orden superior) porque a veces has desestructurado tus accesorios en la parte superior y la solución de Jacob no encaja bien

Descargo de responsabilidad: ninguna afiliación con el propietario del paquete. Simplemente hacer clic decenas de veces para tratar de detectar la diferencia en objetos profundamente anidados es un dolor en el.

ZenVentzi
fuente
4

Ahora hay un gancho para esto disponible en npm:

https://www.npmjs.com/package/use-trace-update

(Divulgación, lo publiqué) Actualización: Desarrollado en base al código de Jacob Rask

Damian Green
fuente
14
Este es prácticamente el mismo código que Jacob publicó. Podría haberle acreditado allí.
Christian Ivicevic
2

El uso de ganchos y componentes funcionales, no solo el cambio de apoyo, puede causar una nueva recuperación. Lo que comencé a usar es un registro bastante manual. Me ayudé mucho Puede que también te resulte útil.

Pego esta parte en el archivo del componente:

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);
  }
};

Al comienzo del método mantengo una referencia de WeakMap:

const refs = useRef(new WeakMap());

Luego, después de cada llamada "sospechosa" (accesorios, ganchos), escribo:

const example = useExampleHook();
checkDep(refs, 'example ', example);
Miklos Jakab
fuente
1

Las respuestas anteriores son muy útiles, en caso de que si alguien está buscando un método específico para detectar la causa de la devolución, encontré esta biblioteca redux-logger muy útil.

Lo que puede hacer es agregar la biblioteca y habilitar la diferencia entre estados (está en los documentos) como:

const logger = createLogger({
    diff: true,
});

Y agregue el middleware en la tienda.

Luego coloque un console.log()en la función de renderizado del componente que desea probar.

Luego, puede ejecutar su aplicación y buscar registros de la consola. Donde sea que haya un registro justo antes, le mostrará la diferencia entre el estado (nextProps and this.props)y podrá decidir si realmente se necesita renderizar allíingrese la descripción de la imagen aquí

Será similar a la imagen de arriba junto con la tecla diff.

pritesh
fuente