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

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

152935 次浏览

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

  • 父组件渲染器
  • 在组件中调用this.setState()。这将触发以下组件生命周期方法shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
  • 组件props的变化。这将触发componentWillReceiveProps > shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate(当Redux存储中有适用的更改时,react-reduxconnect方法将触发此操作)
  • 调用this.forceUpdate,类似于this.setState

你可以通过在shouldComponentUpdate中实现检查来最小化组件的渲染器,并在不需要时返回false

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

@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个道具,通过在render块的开头放置console.log(this.props)来跟踪哪些道具正在改变并不太难。但随着部件越来越复杂,道具越来越多,这种方法就站不住脚了。

下面是一个有用的方法(为了方便使用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)
}

将这个片段添加到组件中可以帮助揭示导致可疑的重新呈现的罪魁祸首,而且很多时候这有助于揭示注入组件的不必要数据。

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

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

上面的答案是非常有用的,以防万一,如果有人正在寻找一个特定的方法来检测渲染的原因,那么我发现这个库还原记录器非常有用。

你能做的就是添加库并启用状态差异(它在文档中),如下所示:

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

并在存储中添加中间件。

然后在要测试的组件的渲染函数中放入console.log()

然后,您可以运行应用程序并检查控制台日志。只要在它之前有一个日志,它就会告诉你状态(nextProps and this.props)之间的差异,你可以决定是否真的需要渲染

它将类似于上面的图像连同diff键。

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

钩子粉丝:

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

附注:我仍然更喜欢使用HOC(高阶组件),因为有时你在顶部解构了你的道具,Jacob的解决方案不太适合

免责声明:与包所有者没有任何联系。只是点击数十次,试图找出深嵌套对象之间的差异,这是一种痛苦。

使用钩子和功能组件,而不仅仅是道具的改变会导致渲染。我开始使用的是一个相当手动的日志。这对我帮助很大。你可能也会发现它很有用。

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

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

然后在每一个“怀疑”之后;call(道具,钩子)

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

你可以使用React Devtools profiler工具检查组件(重新)渲染的原因。不需要更改代码。请参阅react团队的博客文章介绍React分析器

首先,进入设置cog >选择“记录每个组件渲染的原因”;

React Dev Tools >设置”/ > < / ></p>
<p><a href=截图of React Devtools profiler .

多亏了https://stackoverflow.com/a/51082563/2391795的答案,我已经提出了这个稍微不同的解决方案仅功能组件(TypeScript),它也处理状态,而不仅仅是道具。

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;

注意,实现本身并没有太大变化。文档展示了如何在道具/状态和组件中使用它,现在该组件是用TypeScript编写的。