在 React 中,有没有一种系统性的方法来调试导致组件重新渲染的问题?我使用了一个简单的 console.log() 来查看它被渲染的次数,但是我很难弄清楚是什么原因导致组件多次渲染,比如在我的情况下渲染了 4 次。是否存在一种工具,可以显示时间线和/或所有组件树的渲染顺序?
如果您需要一个不依赖于外部库的简短代码片段,我发现这个很有用。
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>;
}
setState(...args) { super.setState(...args) }
覆盖 setState
方法(在类组件中),然后在调试器中设置一个断点,这样您就可以追踪到设置状态的函数。 - redbmkthis.state
是未定义的。 - Jacob RaskuseTraceUpdate
并在我的函数组件中使用它。但它没有检测到道具的变化。该组件仍然会渲染两次(我在其中放置了 console.log("call!")
,并在浏览器的控制台中得到了两个输出)。我还能做些什么? - Marecky首先,进入设置齿轮 > profiler,并选择“记录每个组件重新渲染的原因”
以下是一些 React 组件会重新渲染的例子:
this.setState()
。这将触发以下组件生命周期方法:shouldComponentUpdate
> componentWillUpdate
> render
> componentDidUpdate
props
发生变化。这将触发 componentWillReceiveProps
> shouldComponentUpdate
> componentWillUpdate
> render
> componentDidUpdate
(当 Redux store 中的应用程序发生适用的更改时,react-redux
的 connect
方法会触发此操作)this.setState
的 this.forceUpdate
您可以通过在 shouldComponentUpdate
中实现检查并返回 false
来最小化组件的重新渲染。
另一种方法是使用 React.PureComponent
或无状态组件。纯组件和无状态组件仅在其 props
发生更改时重新渲染。
shouldComponentUpdate
或扩展 React.PureComponent
,以强制仅在更改时重新渲染。 - markeriksonconst MyComponent = (props) => <h1>Hello {props.name}</h1>;
(这是一个无状态组件),它也会在父组件重新渲染时重新渲染。 - jpdelatorreuseContext
钩子实现的上下文消费者中的更改也会导致重新渲染,而不是通过<SomeContext.Consumer>...
。 - elMeroMero@jpdelatorre的回答很好地强调了React组件可能重新渲染的一般原因。
我只是想深入探讨其中一个实例:当props改变时。解决导致React组件重新渲染的问题是一个常见的问题,在我的经验中,很多时候跟踪此问题涉及确定哪些props正在更改。
React组件每当接收到新的props时都会重新渲染。 它们可以像这样接收新的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 个 prop 时,通过在 render
块的开始处加入 console.log(this.props)
来追踪哪些 prop 更改并不太困难。但是对于更复杂的组件和更多的 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 文档。 - Emile Bergeron很奇怪没有人给出这个答案,但我认为它非常有用,特别是因为道具变化几乎总是嵌套很深。
Hooks 狂热粉丝们:
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(高阶组件),因为有时你已经在顶部解构了props,而Jacob的解决方案并不适合。
免责声明:本人与软件包所有者没有任何关联。仅仅是为了尝试在深层嵌套的对象中找到差异而进行无数次点击,真是痛苦不堪。
['a']
实例,则deep_diff
将报告它们未更改,但React会说它们已更改。但是,对于日志输出使用deep_diff
没有问题。 - rpggioimport {
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://dev59.com/CFgR5IYBdhLWcg3wr_AY#51082563
*/
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编写的。
npm
包发布就更好了。 - nachtigall使用钩子和函数组件,不仅仅是属性改变会导致重新渲染。我开始使用的是一种相当手动的日志记录方法。它对我帮助很大。你可能也会发现它有用。
我将此部分复制到组件文件中:
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);
const logger = createLogger({
diff: true,
});
并在store中添加中间件。
然后在您想要测试的组件的渲染函数中放置console.log()
。
然后您可以运行应用程序并检查控制台日志。无论何时有一个日志,它都会在显示状态(nextProps和this.props)
之间的差异,并且您可以决定是否真的需要在那里进行渲染
它将类似于上面的图像,以及diff键。
shouldComponentUpdate
来禁用自动组件更新,然后从那里开始跟踪。更多信息可以在这里找到:https://facebook.github.io/react/docs/optimizing-performance.html - Reza Sadr