redux 跟踪React组件重新渲染的原因

j2cgzkjk  于 2023-08-05  发布在  React
关注(0)|答案(8)|浏览(165)

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

jc3wubiy

jc3wubiy1#

如果你想要一个没有任何外部依赖的简短代码片段,我发现这很有用

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

of1yzvn4

of1yzvn42#

您可以使用React Devtools分析器工具检查组件(重新)呈现的原因。无需更改代码。参见react团队的博客文章Introducing the React Profiler
首先,进入settingscog> profiler,选择“记录每个组件渲染的原因”


的数据


ni65a41a

ni65a41a3#

以下是React组件将重新渲染的一些示例。

  • 父组件重新渲染
  • 在组件内调用this.setState()。这将触发以下组件生命周期方法shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
  • 组件props中的更改。这将触发componentWillReceiveProps > shouldComponentUpdate > componentWillUpdate > render> componentDidUpdatereact-reduxconnect方法在Redux商店中有适用的更改时触发此操作)
  • 调用类似于this.setStatethis.forceUpdate

您可以通过在shouldComponentUpdate内部实现检查并在不需要时返回false来最小化组件的重新呈现。
另一种方法是使用React.PureComponent或无状态组件。纯组件和无状态组件只有在其属性发生更改时才会重新呈现。

9w11ddsr

9w11ddsr4#

@jpdelatorre的回答很好地突出了React组件可能重新渲染的一般原因。
我只想更深入地研究一个例子:当 prop 改变。排除导致React组件重新渲染的原因是一个常见的问题,根据我的经验,很多时候 * 跟踪这个问题涉及到确定哪些 prop 正在更改 *。
React组件在收到新props时会重新渲染。他们可以获得新的 prop ,如:
第一个月
或者如果MyComponent连接到redux存储:

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

字符串
每当prop1prop2prop3prop4的值发生更改时,MyComponent将重新渲染。对于4个props,通过在render块的开头放置console.log(this.props)来跟踪哪些props正在更改并不太困难。但随着构件越来越复杂, prop 越来越多,这种方法就站不住脚了。
下面是一个有用的方法(为了方便使用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)
}


将此代码段添加到组件中可以帮助揭示导致可疑重新呈现的罪魁祸首,并且很多时候这有助于揭示通过管道传输到组件中的不必要数据。

vu8f3i0k

vu8f3i0k5#

奇怪的是,没有人给出这个答案,但我发现它非常有用,特别是因为 prop 的变化几乎总是嵌套得很深。
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} />;
  };
};

字符串
“老”-学校的粉丝:

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


我仍然喜欢使用高阶分量,因为有时你在顶部破坏了你的 prop ,而Jacob的解决方案并不适合
免责声明:与软件包所有者没有任何关系。在嵌套很深的对象中,单击数十次来尝试找出不同之处是一件痛苦的事情。

rjjhvcjd

rjjhvcjd6#

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

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编写的。

nuypyhwy

nuypyhwy7#

使用钩子和函数组件,不仅仅是属性的改变会导致重新呈现。我开始使用的是一个相当手动的日志。它帮了我很多。你可能也会发现它很有用。
我在组件的文件中复制此部分:

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


然后在每个“可疑”调用( prop ,钩子)之后,我写:

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

6xfqseft

6xfqseft8#

上面的答案是非常有帮助的,以防万一,如果有人正在寻找一个具体的方法来检测重新渲染的原因,那么我发现this library redux-logger非常有帮助。
你可以做的是添加库并启用状态之间的差异(它在文档中),如:

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

字符串
并在商店中添加中间件。
然后在要测试的组件的render函数中放入一个console.log()
然后你可以运行你的应用程序并检查控制台日志。只要有一个日志,它就会显示状态(nextProps and this.props)之间的差异,你可以决定是否真的需要渲染

它将类似于上面的图像沿着diff键。

相关问题