React子组件shouldComponentUpdate不生效:父组件setState后仍重渲染
嘿,我之前也碰到过一模一样的坑!咱们一步步来排查解决:
1. 先检查shouldComponentUpdate的基础写法
首先得确认你子组件里的shouldComponentUpdate逻辑是不是写对了——比如是不是不小心返回了true,或者比较逻辑搞反了?
举个正确的浅比较例子,只有当props/state真的变化时才允许渲染:
shouldComponentUpdate(nextProps, nextState) { // 浅比较当前和下一组props/state,不一样才返回true(触发渲染) return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); }
要是你把比较条件写反了,或者漏对比了关键的props,那它自然起不到作用。
2. 重点排查:父组件传递的props是不是每次都变了引用
这绝对是最常见的原因!比如你传递的addons对象/数组,或者像addonsHandler这类回调函数,要是父组件每次渲染时都生成了新的引用,哪怕内容完全没变,子组件的浅比较也会认为props变了,直接触发重新渲染。
先看回调函数的问题
看你父组件的代码,你是直接传this.addonsHandler——这个没问题,类的实例方法引用是固定的。但如果你是在render里用箭头函数包裹(比如() => this.addonsHandler()),那每次父组件渲染都会生成新函数,子组件就会跟着重新渲染。
再看addons这类复杂类型props的问题
你在addonsHandler里执行this.setState({addons: addons})时,如果这个addons是新创建的对象/数组(比如用了扩展运算符...、map、filter这类返回新引用的方法),哪怕内容和之前的state完全一样,引用也变了。这时候子组件的shouldComponentUpdate浅比较this.props.addons === nextProps.addons就会认为props变了,自然会重新渲染。
解决办法有两个:
- 在父组件层面控制更新:更新state前先浅比较内容,只有真的变化时才setState:
addonsHandler = (addons) => { // 用浅比较判断内容是否变化,没变就不更新state if (!shallowEqual(this.state.addons, addons)) { this.setState({ addons }); } };
- 在子组件里做深比较(不推荐,性能开销大,尽量在父组件处理):
shouldComponentUpdate(nextProps) { // 用深比较判断addons内容,而不是引用 return !_.isEqual(this.props.addons, nextProps.addons); }
(这里的_.isEqual可以用lodash的方法,或者自己写个简单的深比较函数)
3. 如果你用了PureComponent/React.memo
要是子组件是React.PureComponent(类组件)或者React.memo(函数组件),它们内部自带浅比较的shouldComponentUpdate逻辑,但同样会栽在复杂类型props的引用变化上,这时候还是得用上面的方法来优化。
比如函数组件用React.memo+自定义比较:
import React, { memo } from 'react'; import _ from 'lodash'; const AntForm = memo((props) => { // 组件渲染内容 return <div>...</div>; }, (prevProps, nextProps) => { // 返回true表示不需要重新渲染,这里自定义比较逻辑 return _.isEqual(prevProps.addons, nextProps.addons) && prevProps.addonsHandler === nextProps.addonsHandler; }); export default AntForm;
4. 排查其他触发渲染的因素
比如子组件自己的state变化,或者父组件调用了forceUpdate强制刷新,这些都会绕过shouldComponentUpdate的判断,得确认有没有这类操作。
内容的提问来源于stack exchange,提问作者zulqarnain

