跟踪React组件重新呈现的原因

时间:2016-12-06 20:50:17

标签: reactjs redux

是否有系统的方法来调试导致组件在React中重新渲染的原因?我放了一个简单的console.log()来查看它渲染了多少时间,但我很难搞清楚导致组件多次渲染的原因,即(4次)。是否存在显示时间轴和/或所有组件树渲染和排序的工具?

9 个答案:

答案 0 :(得分:71)

如果您希望没有任何外部依赖性的简短代码段,我觉得这很有用

componentDidUpdate(prevProps, prevState) {
  Object.entries(this.props).forEach(([key, val]) =>
    prevProps[key] !== val && console.log(`Prop '${key}' changed`)
  );
  Object.entries(this.state).forEach(([key, val]) =>
    prevState[key] !== val && console.log(`State '${key}' changed`)
  );
}

这是我用来跟踪功能组件更新的小钩子

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

答案 1 :(得分:52)

以下是React组件将重新呈现的一些实例。

  • 父组件重新呈现
  • 在组件中调用this.setState()。这将触发以下组件生命周期方法shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate
  • 组件props的更改。这将触发componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate connect react-redux方法会在Redux商店中存在适用的更改时触发此操作)
  • 调用this.forceUpdate,类似于this.setState

您可以通过在shouldComponentUpdate内执行检查并在不需要时返回false来最小化组件的重新呈现。

另一种方法是使用React.PureComponent 或无状态组件。纯粹和无状态的组件只有在它的道具发生变化时才会重新渲染。

答案 2 :(得分:22)

您可以使用 React Devtools 分析器工具轻松检查组件(重新)渲染的原因。无需更改代码。

https://reactjs.org/blog/2018/09/10/introducing-the-react-profiler.html

您需要启用分析器:

enter image description here

Screenshot of React Devtools profiler

答案 3 :(得分:8)

@ jpdelatorre的答案很好地突出了React组件可能重新渲染的一般原因。

我只想更深入地了解一个实例:当道具改变时。排除导致React组件重新渲染的原因是一个常见问题,根据我的经验,很多时候追踪此问题涉及确定哪些道具正在改变

每当收到新道具时,React组件会重新渲染。他们可以收到新的道具,如:

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

MyComponent是否连接到redux商店:

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

任何时候prop1prop2prop3prop4更改MyComponent的值都会重新呈现。使用4个道具,通过在console.log(this.props)块的开头放置render来追踪哪些道具正在变化并不困难。然而,随着更复杂的组件和越来越多的道具,这种方法是站不住脚的。

这是一种有用的方法(为方便起见使用lodash)来确定导致组件重新渲染的道具更改:

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

将此片段添加到您的组件可以帮助揭示导致可疑重新渲染的罪魁祸首,并且很多时候这有助于揭示被传输到组件中的不必要数据。

答案 4 :(得分:5)

使用吊钩和功能组件,而不仅仅是更换道具会导致重新渲染。我开始使用的是相当手动的日志。这对我帮助很大。您可能也会发现它有用。

我将此部分复制到组件文件中:

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

在方法开始时,我保留了WeakMap参考:

const refs = useRef(new WeakMap());

然后在我每次“可疑”调用(道具,挂钩)之后写下:

const example = useExampleHook();
checkDep(refs, 'example ', example);

答案 5 :(得分:1)

以上答案非常有帮助,以防万一有人正在寻找一种特定的方法来检测重新渲染的原因,那么我发现this library redux-logger非常有用。

您可以做的是添加库并启用状态之间的区分(在文档中存在),如:

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

并在商店中添加中间件。

然后在要测试的组件的渲染功能中放置一个console.log()

然后,您可以运行您的应用并检查控制台日志。只要有日志,它就会显示状态(nextProps and this.props)之间的差异,您可以决定是否确实需要渲染enter image description here < / p>

与上图类似,并带有diff键。

答案 6 :(得分:1)

npm上现在有一个钩子可供使用:

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

(公开,我已发布)

答案 7 :(得分:1)

感谢 https://stackoverflow.com/a/51082563/2391795 的回答,我为函数式组件 (TypeScript) 提出了这个略有不同的解决方案,它也处理状态,而不仅仅是 props。

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;

请注意,实现本身并没有太大变化。文档展示了如何将它用于 props/states,并且组件现在是用 TypeScript 编写的。

答案 8 :(得分:-1)

奇怪的是没有人给出答案,但是我发现它很有用,尤其是因为道具的变化几乎总是嵌套在深处。

勾搭迷们:

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

“老”学校迷:

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

P.S。我仍然更喜欢使用HOC(高阶分量),因为有时您在顶部分解了道具,而Jacob的解决方案不太适合

免责声明:与包所有者没有任何隶属关系。只需单击数十次以尝试发现深层嵌套对象中的差异是很痛苦的。