追踪为什么React组件被重新渲染


156

是否有系统的方法来调试导致组件在React中重新渲染的原因?我放置了一个简单的console.log()来查看它渲染了多少时间,但是在弄清楚是什么导致组件多次渲染(例如,我的情况是4次)时遇到了麻烦。是否存在显示时间轴和/或所有组件树渲染和顺序的工具?


也许您可以shouldComponentUpdate用来禁用组件自动更新,然后从那里开始跟踪。更多信息可以在这里找到:facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

@jpdelatorre的答案是正确的。通常,React的优势之一是您可以通过查看代码轻松地跟踪链上的数据流。该阵营DevTools扩展与可以提供帮助。另外,作为Redux附加组件目录的一部分,我还有一些用于可视化/跟踪React组件重新渲染有用工具的列表,以及[React Performance Monitoring](htt
markerikson

Answers:


252

如果您想要一个简短的代码段,而没有任何外部依赖性,那么我觉得这很有用

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

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

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

5
@ yarden.refaeli我认为没有理由要使用if块。简短明了。
以撒

与此同时,如果您发现某个状态正在更新,并且不知道在何处或为什么进行更新,则可以使用覆盖该setState方法(在类组件中),setState(...args) { super.setState(...args) }然后在调试器中设置一个断点,这样您就可以追溯到功能设置状态。
redbmk

我到底如何使用钩子函数?在useTraceUpdate定义好您编写的代码后,我应该在哪里打电话?
戴蒙

在功能组件中,您可以像这样使用它,function MyComponent(props) { useTraceUpdate(props); }并且只要道具发生更改,它就会记录下来
Jacob Rask

1
@DawsonB您可能在该组件中没有任何状态,所以this.state未定义。
Jacob Rask

67

这是一些React组件将重新渲染的实例。

  • 父组件重新渲染
  • this.setState()在组件内调用。这将触发以下组件的生命周期方法shouldComponentUpdate> componentWillUpdate>render >componentDidUpdate
  • 组件的更改props。这将触发componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdateconnect的方法react-redux触发该当存在在终极版存储适用的变化)
  • 调用this.forceUpdate类似于this.setState

您可以通过在内部进行检查shouldComponentUpdate并返回来最大程度地减少组件的重新渲染false在不需要时。

另一种方法是使用React.PureComponent 或无状态的组件。纯的和无状态的组件仅在其道具发生更改时才重新渲染。


6
Nitpick:“无状态”仅表示不使用状态的任何组件,无论它是通过类语法还是函数语法定义的。此外,功能组件始终会重新渲染。您需要使用shouldComponentUpdate或扩展React.PureComponent来仅在更改时强制重新渲染。
markerikson

1
您对无状态/功能组件总是重新渲染是正确的。会更新我的答案。
jpdelatorre

您能否澄清,何时以及为何总是重新渲染功能组件?我在应用程序中使用了很多功能组件。
jasan

因此,即使您使用功能性的方式来创建组件,例如const MyComponent = (props) => <h1>Hello {props.name}</h1>;(这是一个无状态组件)。每当父组件重新渲染时,它将重新渲染。
jpdelatorre

2
当然,这是一个很好的答案,但不能回答真正的问题,即-如何跟踪触发重新渲染的原因。雅各布·R(Jacob R)的答案看起来很有希望为实际问题提供答案。
萨努伊,

10

@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')
  }
}

任何时候的价值prop1prop2prop3,或prop4改变MyComponent将重新呈现。有了4个道具,通过将a 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)
}

将此片段添加到您的组件中可以帮助发现导致可疑重新渲染的罪魁祸首,而且很多时候,这有助于阐明不必要的数据被管道传输到组件中的情况。


3
现在已调用UNSAFE_componentWillReceiveProps(nextProps)它,不推荐使用。“此生命周期以前已命名componentWillReceiveProps。该名称将继续使用到版本17。” 来自React文档
Emile Bergeron

1
您可以使用componentDidUpdate达到相同的效果,无论如何,这还是可以说是更好的,因为您只想找出导致组件实际更新的原因。

5

奇怪的是没有人给出这个答案,但是我发现它非常有用,特别是因为道具更改几乎总是嵌套在深处。

勾子迷们:

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

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

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



2

使用挂钩和功能组件,不仅更改道具也可能导致重新渲染。我开始使用的是相当手工的日志。我帮了我很多 您可能也会发现它很有用。

我将此部分粘贴到组件的文件中:

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

1

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

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

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

并在商店中添加中间件。

然后console.log()在要测试的组件的render函数中放置一个。

然后您可以运行您的应用并检查控制台日志。只要有日志,它就会显示状态之间的差异(nextProps and this.props),您可以决定是否真的需要渲染在此处输入图片说明

它将与diff键相似于上图。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.