组件优化
分类
- 父组件更新导致子组件渲染
- Props的错误写法导致组件渲染
- Context的更新导致组件渲染
原理
组件性能优化,一般就是优化子组件,优化子组件的不相关的 render(渲染)
思路
避免重新渲染的思想是对比Props的引用:
-
父组件更新会更新子组件的引用
- 使用PureComponent,memo,useMemo,children(不推荐)
-
子组件直接传递引用类型,每次的props引用也是新的
-
使用变量定义引用类型,再进行传递,变量的引用是不会变得,除非内部的值变了——针对类组件和函数的
useState定义的状态变量state = { count: 0, pass: { a: 1 }, } handleClick = () => { console.log('handleClick') // 只要父组件调用的方法会改变要传给子组件的值,哪怕是一模一样的也会重新渲染子组件,因为引用变了,所以这种情况只是适用于值不变的情况 // this.setState({ count: ++this.state.count, pass: { a: 2, b: 3 } }) this.setState({ count: ++this.state.count, pass:{a:1} }) } ``` -
useMemo,useCallback——函数组件的解决办法
- useMemo缓存值
- useCallback缓存函数
-
父组件更新导致子组件渲染
1. class 组件
-
shouldComponentUpdate( state, props )// 基于传入的 props 做判断 -
PureComponent——就是React会自动帮我们执行 shouldComponentUpdate 对Props进行浅比较优化更新。父组件中的
state发生变化,执行了React.createElement(Son),所得到的的props的引用每次都是新的,因此会引发重新渲染。【正常,但是要避免】 -
children方式 ——不推荐
2. 函数组件
Function组件 的特性是每一次的组件重新渲染,都会重新执行一次函数。而对于Class组件来说,只会执行一遍 new Class ,其实仔细想想还是挺可怕的。对函数组价来说,每次的执行都意味着新的上下文,新的变量,新的作用域。因此我们要更加注重函数组件的性能优化。
-
memo() 包裹子组件
- memo这个api, 主要是对比props引用是否改变,从而避免子组件的重新渲染
-
useMemo 计算逻辑优化,
-
可以包裹子组件,对子组件进行优化,第二个参数为传依赖数组;也可以用于缓存值
-
作者力推:useMemo 保存了组件的引用,没有重新执行函数组件,因此避免了组件内的变量,函数声明,和作用域的声明。从而优化了性能
-
-
children——不建议
// 测试时的代码——可以移步参考文献(更详细)
import React, { Component, PureComponent, useState, useEffect, useMemo, memo } from 'react'
import { Button } from 'antd'
// 父组件为函数组件
// const Detail = () => {
// const [count, setCount] = useState(0)
// useEffect(() => {
// console.log(window.location, 'window.location')
// }, [])
// return (
// <div>
// <Button onClick={() => setCount((preData) => ++preData)}>点击</Button>
// <div>{count}</div>
// {/* 第一个元素是要缓存的组件,第二个是依赖数组 */}
// {useMemo(
// () => (
// <UseMemoChild />
// ),
// []
// )}
// <MemoChild/>
// <Child />
// </div>
// )
// }
// 父组件为类组件
class Detail extends Component {
state = {
count: 0,
}
render(): React.ReactNode {
return (
<div>
<Button onClick={() => this.setState({ count: ++this.state.count })}>点击</Button>
<div>{this.state.count}</div>
<Child />
<MemoChild />
<ClassChild />
<PureClassChild />
</div>
)
}
}
// 错误
const Child = () => {
useEffect(() => {
console.log('Child')
})
return <div>Child</div>
}
// 正确
const MemoChild = memo(() => {
useEffect(() => {
console.log('MemoChild')
})
return <div>Child</div>
})
// 正确——并且只能在函数中使用
const UseMemoChild = () => {
useEffect(() => {
console.log('UseMemoChild')
})
return <div>Child</div>
}
// 错误
class ClassChild extends Component {
componentDidUpdate() {
console.log('ClassChild')
}
render(): React.ReactNode {
return <div>ClassChild</div>
}
}
// 正确——并且只能在类中使用
class PureClassChild extends PureComponent {
componentDidUpdate() {
console.log('PureClassChild')
}
render(): React.ReactNode {
return <div>ClassChild</div>
}
}
export default Detail
props引起的子组件渲染
-
错误写法——禁止这样写
这个解决办法只有类组件和函数组件的
useState定义的变量生效
<MemoChild count={{ a: 1 }} handleOk={() => {}} />
为什么会引起渲染?因为组件的渲染主要是通过监听props和state的变化进行渲染的,在这里props每次都是一个新的对象,因为引用的不同,每次父组件的渲染都会导致子组件的渲染
定义好变量然后再传
<MemoChild count={this.state.pass} handleOk={this.handleClick} />
直接将变量传给子组件,因为变量的引用不修改的话是不会变得,因此props就是没有变化的,也就不会重新渲染子组件了。
但是如果变量内部变了,也是会引发渲染的(要不然子组件怎么渲染啊!!!),但避免了值没变就渲染子组件,浪费性能。
-
可以使用的优化——不要过度使用
函数组件可以生效的办法
使用了 useCallback && useMemo 这个两个优化Hook,主要就是根据依赖是否改变来确定是否要更新值的变化,以保证值的引用不变。这中写法适合于大部分的写法
const anyMethod = useCallback(() => {}, []);
const [componentDetails] = useMemo(() => {
const componentDetails = { name: "子组件" };
return [componentDetails];
}, []);
函数组件的优化和类组件有什么不同
函数组件每次更新都会执行所有的代码
// 函数每次更新都会重新执行所有的代码,(只有useState中的状态会保留)不像类组件只执行一次,类组件就不会重新执行下面的代码
console.log('重新执行函数的代码了')
// 直接定义然后传值的话,
const pass2 = { a: 1 }
但类组件不会重新挂载,只是触发更新而已
componentDidMount() {
// 不会重新挂载
console.log('重新执行函数的代码了---mounted')
}
componentDidUpdate() {
console.log('重新执行函数的代码了---update')
}
因此函数组件的优化更重要
Function组件 的特性是每一次的组件重新渲染,都会重新执行一次函数。而对于Class组件来说,只会执行一遍 new Class ,其实仔细想想还是挺可怕的。对函数组价来说,每次的执行都意味着新的上下文,新的变量,新的作用域。因此我们要更加注重函数组件的性能优化。
函数组件要优化,必须使用useMemo,useCallback进行优化