Trace why a React component is re-rendering

2020-01-30 02:36发布

问题:

Is there a systematic approach to debug what is causing a component to re-render in React? I put a simple console.log() to see how many time it renders, but am having trouble figuring out what is causing the component to render multiple times i.e (4 times) in my case. Is there a tool that exists that shows a timeline and/or all components tree renders and order?

回答1:

If you want a short snippet without any external dependencies I find this useful

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

Here is a small hook I use to trace updates to function components

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


回答2:

Here are some instances that a React component will re-render.

  • Parent component rerender
  • Calling this.setState() within the component. This will trigger the following component lifecycle methods shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
  • Changes in component's props. This will trigger componentWillReceiveProps > shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate (connect method of react-redux trigger this when there are applicable changes in the Redux store)
  • calling this.forceUpdate which is similar to this.setState

You can minimize your component's rerender by implementing a check inside your shouldComponentUpdate and returning false if it doesn't need to.

Another way is to use React.PureComponent or stateless components. Pure and stateless components only re-render when there are changes to it's props.



回答3:

@jpdelatorre's answer is great at highlighting general reasons why a React component might re-render.

I just wanted to dive a little deeper into one instance: when props change. Troubleshooting what is causing a React component to re-render is a common issue, and in my experience a lot of the times tracking down this issue involves determining which props are changing.

React components re-render whenever they receive new props. They can receive new props like:

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

or if MyComponent is connected to a redux store:

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

Anytime the value of prop1, prop2, prop3, or prop4 changes MyComponent will re-render. With 4 props it is not too difficult to track down which props are changing by putting a console.log(this.props) at that beginning of the render block. However with more complicated components and more and more props this method is untenable.

Here is a useful approach (using lodash for convenience) to determine which prop changes are causing a component to re-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)
}

Adding this snippet to your component can help reveal the culprit causing questionable re-renders, and many times this helps shed light on unnecessary data being piped into components.



回答4:

There's now a hook for this available on npm:

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

(Disclosure, I published it)



回答5:

Strange nobody has given that answer but I find it very useful, especially since the props changes are almost always deeply nested.

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

"Old"-school fanboys:

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

P.S. I still prefer to use HOC(higher order component) because sometimes you have destructured your props at the top and Jacob's solution doesn't fit well

Disclaimer: No affiliation whatsoever with the package owner. Just clicking tens of times around to try to spot the difference in deeply nested objects is a pain in the.



回答6:

The above answers are very helpful, just in case if anyone is looking for a specfic method to detect the cause of rerender then I found this library redux-logger very helpful.

What you can do is add the library and enable diffing between state(it is there in the docs) like:

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

And add the middleware in the store.

Then put a console.log() in the render function of the component you want to test.

Then you can run your app and check for console logs.Wherever there is a log just before it will show you difference between state (nextProps and this.props) and you can decide if render is really needed there

It will similar to above image along with the diff key.



标签: reactjs redux