背景
在父组件中使用了子组件,但当父组件任一state
发生变化时,子组件都会重新进行渲染。并且是包括所有子组件在内的全部重渲染,无论某些子组件是否有用到 state
中的值。
类组件中的shouldComponentUpdate
shouldComponentUpdate的基本概念
shouldComponentUpdate
是一个生命周期函数,在每次render()
函数执行之前被调用,其返回的布尔值决定了是否需要执行render()
函数。
shouldComponentUpdate的注意点
- 组件的首次渲染或者调用
forceUpdate()
方法时不会触发调用shouldComponentUpdate
方法; - 该生命周期函数的默认行为是在每次
state
发生变化时触发重新渲染,如果自行声明该函数会覆盖这一默认行为,需要自行判断state
的变化以决定是否重新渲染;
shouldComponentUpdate的使用
class MyComponent extends React.Component { state = { count: 0 }; shouldComponentUpdate(nextProps, nextState) { // 无需手动更新 state 值,组件会自动更新 // this.setState({ ...nextState }); if (nextState.count <= 3) { // count 值大于 3 后,组件便不再更新 return true; } else { return false; } } render() { const { count } = this.state; return ( <button onClick={() => this.setState({ count: count + 1 })}> {count} </button> ); } } 复制代码
类组件中的PureComponent
React.PureComponent
类似于内置了shouldComponentUpdate
逻辑的 React.Component
,它会同时对 props
和 state
的变化前和变化后的值进行浅对比
,如果都没发生变化则会跳过渲染。
PureComponent的注意点
注意点-1.保证所有子组件都是纯组件
举个例子,下面的纯组件包含一个展示当前时间的子组件:
class Counter extends React.Component { state = { count: 0 }; render() { const { count } = this.state; return ( <div style=> <div>count: {count}</div> <ConstText count={count > 2 ? count : 0} /> <button onClick={() => this.setState({ count: count + 1 })}>Add</button> </div> ); } } // “纯”组件 class ConstText extends React.PureComponent { render() { const { count } = this.props; const d = new Date(); const time = `${d.getHours()}: ${d.getMinutes()}: ${d.getSeconds()}`; console.log('pure rendered', count); return ( <div> pure: {count} <ConstChild time={time} /> </div> ); } } // 展示时间的子组件 class ConstChild extends React.Component { render() { const { time } = this.props; console.log('child rendered', time); return <div>{time}</div> } } 复制代码
页面初始化时:
前两次点击按钮后:
此时纯组件和其子组件都未触发更新,在第三次点击后,才同时触发更新:
因此,需要组件在相同 props
传入值的情况下总会有相同的渲染内容,也就是纯组件中 Pure
的含义所在,它有些类似纯函数的定义(传入相同的参数执行后,总会得到相同的返回值)。
注意点-2.props值为对象时的变化识别
PureComponent
是对 props
的变化前后的值进行浅对比来决定是否重渲染组件,如果值类型是复杂类型,如引用类型(对象),并不会深入遍历每个属性的变化,即使这个引用类型的实际值发生了变化,但是由于引用地址未变化,组件依然会被判断为未变化,导致不会重新渲染。
注意点-3.props值为一个返回某个值的函数
<Counter count={() => 3} /> 复制代码
即使每次函数实际执行的值都是相同的,也都会触发渲染,因为这个函数本身每次都会被判断为一个新值,使得性能优化失效;
函数组件中的memo
React.memo
是一个类似 PureComponent
的高阶组件,用于函数组件
函数组件中的useMemo
与useCallback
当子组件使用React.memo
包裹起来之后,如果子组件用到了容器组件的某个引用类型的变量或者函数,那么当容器内部的state更新之后,这些变量和函数都会重新赋值,这样就会导致即使子组件使用了memo包裹也还是会重新渲染,那么这个时候我们就需要使用useMemo
和useCallback
了。
useMemo
缓存变量useCallback
缓存回调函数
import React, { memo, useState, useEffect, useMemo } from 'react' const Home = (props) => { const [a, setA] = useState(0) const [b, setB] = useState(0) useEffect(() => { setA(1) }, []) const add = useCallback(() => { console.log('b', b) }, [b]) const name = useMemo(() => { return b + 'xuxi' }, [b]) return <div><A n={a} /><B add={add} name={name} /></div> } 复制代码
此时a更新后B组件不会再重新渲染。
函数组件中的useRef
useRef返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。