一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情
背景
父组件的数据变化了,setState()会引起所有后代子组件也被更新,但是子组件没有任何变化的时候也会重新被渲染,那么如何避免不必要的渲染呢?
类组件
shouldComponentUpdate钩子
每个组件都有这个钩子函数
作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
-
触发时机:render之前
-
参数:它有两个形参
- 第一个参数:当前即将要使用的props
- 第二个参数:当前即将要使用的state
class Hello extends Component {
shouldComponentUpdate() {
// 根据条件,决定是否重新渲染组件
return false
}
render() {…}
}
并不是所有的子组件都一定要随着祖先组件的更新而更新
shouldComponentUpdate用来自定义更新逻辑
纯组件
纯组件:React.PureComponent 与 React.Component功能相似
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部自动通过对比前后两次 props 和 state 的值,来决定是否重新渲染组件
// class Hello extends React.PureComponent { // 普通组件
class Hello extends React.PureComponent { // 纯组件
render() {
return (
<div>纯组件</div>
)
}
}
只有在性能优化的时候可能会用到纯组件,不要所有的组件都使用纯组件,因为纯组件需要消耗性能进行对比
函数组件
当你想要避免函数组件 props 没有变化而产生的不必要更新时,就要用到 React.memo 了
记忆组件上一次的渲染结果,在 props 没有变化时复用该结果,避免函数组件不必要的更新
格式
const MemoChild = React.memo(Child)
说明:
- React.memo 是一个高阶组件,用来记忆(memorize)组件。
- 参数(Child):需要被记忆的组件,或者说是需要避免不必要更新的组件。
- 返回值(MemoChild):React 记住的 Child 组件。
原理:通过对比检查更新前后 props 是否相同,来决定是否复用上一次的渲染结果,
- 如果相同,复用上一次的渲染结果;
- 如果不同,重新渲染组件。
例子:
function Son1 () {
console.log('son1.')
return (
<div>
Son1
</div>
)
}
export default React.memo(Son1)
浅层对比
默认情况下,React.memo 只会对更新前后的 props 进行浅对比(shallow compare)与 PureComponent 相同。
也就是说,对于对象类型的 prop 来说,只会比较引用
- 如果更新前后的引用相同,复用上一次的渲染结果(不会重新渲染该组件)
- 如果更新前后的引用不同,重新渲染该组件
例子:
import { useState } from "react"
import Son1 from './Son1'
import Son2 from './Son2'
export default function App(){
const [count, setCount] = useState(0)
const [obj, setObj] = useState({a:1})
console.log('父组件')
const obj2 = obj
obj2.a = obj.a + 1
return (
<div>
<p>计数器:{count}, {obj.a}</p>
<button onClick={() => {setObj(obj2); setCount(count+1)}}>+1</button>
<Son1 count={obj}/>
<Son2 />
</div>
)
}
手动控制比较
如果你要手动控制比较过程,可以使用 React.memo 的第二个参数:
说明:
- 第二个参数:用来比较更新前后 props 的函数
- 返回值:如果返回 true,表示记住(不重新渲染)该组件;如果返回 false,表示重新渲染该组件
问题:
传入的函数永远是最新的
import {useState} from 'react'
import Son1 from './Son1'
export default function Index() {
const [num, setnum] = useState(1)
const addNum = () => {
setnum(num + 1)
}
return (
<div>
num:{num}<button onClick={()=>setnum(num+1)}>num+1</button>
<Son1 addNum={addNum}></Son1>
</div>
)
}
由于addNum是定义的函数,而在整个组件重新渲染的过程了,它被重新定义了,所以它会导致Son1组件的更新
useCallback
使用场景:在使用 React.memo 时,为了组件每次更新时都能获取到相同引用的函数,就要用到 useCallback Hook
作用:记忆传入的回调函数,这个被记住的回调函数会一直生效,直到依赖项发生改变
格式:
const memoCallback = useCallback(()=>{
doSomething(a,b)
}, [a,b])
说明:
-
第一个参数:必选,需要被记忆的回调函数。
-
第二个参数:必选,依赖项数组,用于指定回调函数中依赖(用到)的数据(类似于 useEffect 的第二个参数)。
即使没有依赖,也得传入空数组([ ]),此时,useCallback 记住的回调函数就会一直生效。
-
返回值:useCallback 记住的回调函数。
例子:
import {useCallback, useState} from 'react'
import Son1 from './Son1'
export default function Index() {
const [num, setnum] = useState(1)
const addNum = useCallback(() => {
setnum(num + 1)
},[num])
return (
<div>
num:{num}<button onClick={()=>setnum(num+1)}>num+1</button>
<Son1 num={num} addNum={addNum}></Son1>
</div>
)
}
import React from 'react'
function Son1(props) {
console.log('son1.')
return (
<div>
Son1, <button onClick={props.addNum}>+1</button>
</div>
)
}
export default React.memo(Son1)
useCallback需要配置React.memo使用才有意义,不然反而性能更低,因为useCallback来包裹函数也是需要开销的
useMemo
使用场景
类似于 useCallback,可以在组件更新期间保持任意数据引用相等,一般用来处理对象类型的数据
对比:useCallback 只能记忆函数,而 useMemo 可以记忆任意数据。
作用:记忆任意数据,这个被记住的数据会一直生效,直到依赖项发生改变
const memoValue = useMemo(()=>需要记住的数据, deps)
语法
- 第一个参数:必选,回调函数。注意:该回调函数会被调用,并通过返回值指定需要被记住的数据。
- 第二个参数:必选,依赖项数组,用于指定回调函数中依赖(用到)的数据。同样,没有依赖项时,传入空数组([])。
- 返回值:useMemo 记住的数据
- useMemo 记住的数据会一直生效(或者说会一直返回同一个数据),直到依赖项发生改变。
示例-优化前
import {useState} from 'react'
function fn(n) {
let total = 0
for(let i =0; i<n; i++) {
total += i
}
return total
}
export default function Index() {
const [num, setnum] = useState(10)
const [a, setA] = useState(1)
const total = fn(num)
// useMemo( ()=> 100 * 200, [])
return (
<div>
<p>{total}</p>
<p>a:{a}<button onClick={()=>setA(a+1)}>a+1</button></p>
<p>num:{num}<button onClick={()=>setnum(num+1)}>sum+1</button></p>
</div>
)
}
优化:
const total = useMemo(()=>fn(num), [num])
- 如果处理的是函数,推荐使用 useCallback。
- 如果处理的是其他数据(比如,对象),推荐使用 useMemo