是否有系统的方法来调试导致组件在 React 中重新渲染的原因?我放了一个简单的 console.log() 来查看它渲染了多少次,但是我无法弄清楚是什么导致组件多次渲染,即在我的例子中(4 次)。是否存在显示时间线和/或所有组件树渲染和顺序的工具?
shouldComponentUpdate
禁用自动组件更新,然后从那里开始您的跟踪。可在此处找到更多信息:facebook.github.io/react/docs/optimizing-performance.html
如果你想要一个没有任何外部依赖的简短片段,我觉得这很有用
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>;
}
您可以使用 React Devtools 分析器工具检查组件(重新)渲染的原因。无需更改代码。请参阅 React 团队的博文 Introducing the React Profiler。
首先,转到设置 cog > profiler,然后选择“记录每个组件呈现的原因”
https://i.stack.imgur.com/HCpFy.png
https://i.stack.imgur.com/pQIKi.gif
以下是 React 组件将重新渲染的一些实例。
父组件重新渲染
在组件内调用 this.setState()。这将触发以下组件生命周期方法 shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate
组件道具的变化。这将触发 componentWillReceiveProps > shouldComponentUpdate > componentWillUpdate > render > componentDidUpdate (react-redux 的 connect 方法会在 Redux 存储中有适用的更改时触发此操作)
调用类似于 this.setState 的 this.forceUpdate
您可以通过在 shouldComponentUpdate
中实施检查并在不需要时返回 false
来最小化组件的重新呈现。
另一种方法是使用 React.PureComponent
或无状态组件。纯无状态组件仅在其 props 发生更改时重新渲染。
shouldComponentUpdate
或扩展 React.PureComponent
,以强制仅在更改时重新呈现。
const MyComponent = (props) => <h1>Hello {props.name}</h1>;
(这是一个无状态组件)。每当父组件重新渲染时,它都会重新渲染。
useContext
-hook 而不是 <SomeContext.Consumer>...
实现时,上下文使用者的任何更改也会导致重新呈现。
@jpdelatorre 的回答非常适合突出 React 组件可能重新渲染的一般原因。
我只是想更深入地研究一个实例:当道具发生变化时。对导致 React 组件重新渲染的原因进行故障排除是一个常见问题,根据我的经验,很多时候跟踪此问题涉及确定哪些 props 正在更改。
React 组件在收到新的 props 时会重新渲染。他们可以收到新的道具,例如:
<MyComponent prop1={currentPosition} prop2={myVariable} />
或者如果 MyComponent
连接到 redux 存储:
function mapStateToProps (state) {
return {
prop3: state.data.get('savedName'),
prop4: state.data.get('userCount')
}
}
任何时候 prop1
、prop2
、prop3
或 prop4
的值更改 MyComponent
都会重新呈现。对于 4 个 props,通过在 render
块的开头放置一个 console.log(this.props)
来跟踪哪些 props 正在更改并不难。然而,随着组件越来越复杂,道具越来越多,这种方法是站不住脚的。
这是一个有用的方法(为了方便,使用 lodash)来确定哪些 prop 更改导致组件重新渲染:
componentWillReceiveProps (nextProps) {
const changedProps = _.reduce(this.props, function (result, value, key) {
return _.isEqual(value, nextProps[key])
? result
: result.concat(key)
}, [])
console.log('changedProps: ', changedProps)
}
将此代码段添加到您的组件可以帮助揭示导致可疑重新渲染的罪魁祸首,并且很多时候这有助于阐明不必要的数据被输送到组件中。
UNSAFE_componentWillReceiveProps(nextProps)
并且已被弃用。 “此生命周期以前被命名为 componentWillReceiveProps
。该名称将继续有效,直到版本 17。” 来自 React documentation。
奇怪的是没有人给出这个答案,但我发现它非常有用,特别是因为道具更改几乎总是嵌套得很深。
钩子迷们:
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 的解决方案并不适合
免责声明:与包所有者没有任何关系。只需单击数十次以尝试发现深层嵌套对象的差异是一种痛苦。
['a']
实例,deep_diff
会报告它们未更改,但 React 会说它们已更改。不过,使用 deep_diff
作为日志输出没有问题。
使用钩子和功能组件,不仅道具更改会导致重新渲染。我开始使用的是一个相当手动的日志。这对我帮助很大。您可能会发现它也很有用。
我将此部分复制到组件的文件中:
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);
感谢 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 编写的。
npm
包发布的,那就太好了。
上面的答案非常有帮助,以防万一有人正在寻找一种特定的方法来检测重新渲染的原因,那么我发现 this library redux-logger 非常有帮助。
您可以做的是添加库并启用状态之间的差异(它在文档中),例如:
const logger = createLogger({
diff: true,
});
并在 store 中添加中间件。
然后在要测试的组件的渲染函数中放置一个 console.log()
。
https://i.stack.imgur.com/QwR2o.png
它将类似于上图以及 diff 键。
不定期副业成功案例分享
setState(...args) { super.setState(...args) }
覆盖setState
方法(在类组件中),然后在调试器中设置断点然后您将能够追溯到设置状态的函数。useTraceUpdate
之后,我究竟应该在哪里调用它?this.state
未定义。useTraceUpdate
并在我的函数组件中使用了它。但它没有检测到道具发生了变化。并且组件仍然呈现两次(我将console.log("call!")
放入其中,并在浏览器的控制台中得到两个打印输出。我还能做什么?