react性能优化之异常render次数
react性能优化之异常render次数
react工程的性能优化部分:
除了前端通用的,比如减少dom节点,懒加载,按需加载之外。
react有特别之处就是:可能会有莫名的页面假死(不能操作)。大多情况都是过多的意外render,导致过多的js执行,阻塞了ui的渲染
以下4种常见的情况,容易触发过多异常render,损害性能
使用pure组件(React.PureComponent / React.Memo)
不使用pure组件(React.PureComponent / React.Memo),容易造成过多异常的render
举例
class App extends React.Component { constructor() { super() this.state = { aa: 123, bb: 222, obj: {a: 1} } } render () { console.log('render App') return ( <Provider store={store}> <div className="App" onClick={() => { // 此处点击触发,A B 子组件都会触发render this.setState({ aa: 123 }) }}> <A aa={this.state.aa} bb={this.state.obj}/> <B bb={this.state.bb}/> </div> </Provider> ); } } // 子组件A class A extends React.Component { render () { console.log('render A') console.log(this.props) return ( <div> {this.props.aa} </div> ); } } // 子组件B const UserContainer = (props) => { console.log('render 222') console.log(props) return ( <div> <h2>{props.bb}</h2> </div> ) } 复制代码
问题点:
当我们点击 className="App" 内容区,触发了 this.setState 后,A 和 B 组件都异常重新render了!!
我们理想的目标是:
A和B都不触发渲染,因为 this.state.bb 根本没动, this.state.aa的值也没变
此处如果不是onClick事件,是onScoll 或 onResize 这种不做防抖的话,很容易触发N次,然后会触发N次render,页面容易直接卡死
解决办法:用 React.PureComponent 或 React.Memo
会自动对props和state的值做一个浅比较。如果值的前后没改变,则不会触发render(另外插一嘴vue没有这个问题,可以理解为,vue自动已经处理了)
另外使用了context的话,还是会穿透 并 触发render。是react的正常render。所以context不能滥用(官方推荐实用场景:当前认证的用户、主题或首选语言)
如果想要控制某些props,即使被改变,也不触发render。做法是:类组件用shouldComponentUpdate。函数组件用React.Memo的第二个参数控制。可以自行了解
避免使用匿名函数
function App() { const [aa, setAa] = useState(123) return ( <div className="App"> // 子组件A <A onClick={() => { console.log('我可能会发请求调接口, 然后处理很多逻辑') }} /> <div onClick={() => setAa(aa+1)}>点击触发render</div> </div> ); } export default React.memo(App); 复制代码
问题点:
当前组件每次render的时候,匿名函数都会被重新创建,会导致A组件被触发render(特别是当A组件放在循环内,更是会触发多次)
我们想要的应该是:
A组件不会被触发render
解决办法:
不用匿名函数,用 函数的引用 + useCallback 即可 (把函数引用缓存起来,否则每次render都会重新声明 函数引用)
function App() { const [aa, setAa] = useState(123) const fn = useCallback(() => { console.log('发请求调接口, 然后处理很多逻辑') }, []) return ( <div className="App"> // 子组件A <A onClick={fn} /> <div onClick={() => setAa(aa+1)}>点击触发render</div> </div> ); } export default React.memo(App); 复制代码
避免使用内联对象
原因是:每次render时,对象的引用都会改变。一但引用发生改变,会导致以下A、B、C 3个子组件都触发不必要的render
function App() { const [aa, setAa] = useState(123) console.log('render App') const style = { margin: 0 } const propsObj = { someProp: 'someValue', a: {} } return ( <div> <A style={{ margin: 0 }} /> <B style={style} /> <C {...propsObj} /> <div onClick={() => setAa(aa+1)}>点击我,触发App组件的render</div> </div> ); } export default React.memo(App); 复制代码
改进写法
静态的对象,可以放组件外面
或者对象统一由useState声明(class组件是放state内),useState内部会保存当前对象状态,并做浅比较
const style = { margin: 0 } // 静态的对象,可以放App组件外面 function App() { const [aa, setAa] = useState(123) console.log('render App') const [bb, setBb] = useState({ someProp: 'someValue', a: {} }) return ( <div> <A style={style} {...bb} /> <div onClick={() => setAa(aa+1)}>点击我,触发App组件的render</div> </div> ); } export default React.memo(App); 复制代码
不要再react控制范围之外,多次setState
比如在setTimeout内,执行N次setState,就会触发N次render。理想的情况是,只异步render 一次
详细可以看我另一篇:juejin.cn/post/706215…
作者:bigtree
链接:https://juejin.cn/post/7068877716224901128