开发环境:React Native + Taro + Typescript
useCallback的用途,主要用于性能优化:
1 避免不必要的子组件重渲染:当父组件重渲染时,如果传递给子组件的函数每次都是新创建的,即使子组件使用了 React.memo,也会导致子组件重新渲染。
2 作为其他 Hook 的依赖项:当函数被用作 useEffect、useMemo 或其他 Hook 的依赖项时,使用 useCallback 可以确保函数引用稳定,避免不必要的 effect 执行。
3 记忆化函数:对于创建成本较高的函数,使用 useCallback 可以避免在每次渲染时都重新创建。
useCallback的优势:
1 性能优化:通过缓存函数实例,减少不必要的重渲染和函数创建,提高应用性能。
2 避免无限循环:在 useEffect 中使用函数时,如果函数没有使用 useCallback 包装,可能会导致无限重渲染。
3 稳定的函数引用:确保在依赖项不变的情况下,函数引用保持不变,这对于优化子组件渲染特别有用。
使用时的注意事项:
1 不要过度使用:只有在确实需要优化性能时才使用 useCallback,因为useCallback本身也有性能开销。
2 正确设置依赖数组:确保依赖数组中包含所有在函数内部使用且可能变化的值,否则可能导致闭包问题。
3 与 React.memo 配合使用:useCallback 通常与 React.memo 一起使用才能发挥最大效果。
下面是父组件:UseCallback
import React, { useState, useCallback, useEffect } from 'react'
import { View, Text, Button, ScrollView } from '@tarojs/components'
import styles from './plant.module.scss'
import Strawberry from './UseCallbackSub'
const PlantManager: React.FC = ()=>{
useEffect(()=>{
console.log("父组件:useEffect, 组件重渲染!!!")
})
// 草本植物科目数量 初始化
const [subjectNum, setSubjectNum] = useState(0)
// 蔷薇科的植物或水果的颜色 初始化
const [rosaceaeColor, setrosaceaeColor] = useState('green')
// 蔷薇科的植物或水果size大小 初始化
const [rosaceaeSize, setRosaceaeSize] = useState(5)
// 蔷薇科植物或水果的颜色 改变次数,需要改变草莓颜色时,需要在此加一
const [changeColorNum, setChangeColorNum] = useState(0)
// 没有使用useCallback的函数,每次渲染都会创建新的函数实例
const handleInrement = () => {
console.log('父组件 handleInrement')
// setSubjectNum(preSubjectNum => preSubjectNum + 1);
}
// 使用useCallback缓存函数,同样改变subjectNum的值
const handleUseCallbackChangeSubjectNum = useCallback(()=>{
console.log('父组件 handleUseCallbackChangeSubjectNum')
// setSubjectNum(preSubjectNum => preSubjectNum + 1);
}, []); //这里的依赖数组为空,是因为setSubjectNum是稳定的
// 使用useCallback缓存函数,并带有依赖项
// 改变子组件中草莓的颜色,并查看子组件渲染的时机与次数
const handleChangeRosaceaeColor = useCallback(()=>{
const color = rosaceaeColor === 'green' ? 'green' : 'red'
setrosaceaeColor(color)
}, [changeColorNum])
const handleChangeColorNum = useCallback(()=>{
setChangeColorNum(preNum => preNum + 1)
}, [])
// 使用useCallback缓存函数,没有依赖项
// 改变子组件中草莓的size大小,并查看子组件渲染的时机与次数
const handleChangeRosaceaeSize = useCallback(()=>{
setRosaceaeSize(preSize => preSize + 1)
}, [])
return (
<ScrollView
scrollY
scrollWithAnimation
className={styles.scroll}
style={{
height: '40vh',
}}
>
<View className={styles.container}>
<View className={styles.showTextBox}>
<Text>草本植物的数量 subjectNum = {subjectNum}</Text>
<Text>蔷薇科植物颜色需要改变的次数 = {changeColorNum}</Text>
<Strawberry
color={rosaceaeColor}
increment={handleInrement}
changeSubjectNum={handleUseCallbackChangeSubjectNum}
size={rosaceaeSize}>
</Strawberry>
</View>
<View className={styles.showTextBox}>
<Text className={styles.showText}>使用useCallback带有依赖项,改变子组件中草莓的颜色</Text>
<Button onClick={handleChangeRosaceaeColor} className={styles.actionBox} >改变子组件的草莓颜色</Button>
<Button onClick={handleChangeColorNum} className={styles.actionBox}>需要改变蔷薇科植物颜色时,给changeColorNum加一</Button>
</View>
<View className={styles.showTextBox}>
<Text className={styles.showText}>使用useCallback没有带依赖项,改变子组件中草莓size大小</Text>
<Button className={styles.actionBox} onClick={handleChangeRosaceaeSize}>改变子组件草莓的size</Button>
</View>
</View>
</ScrollView>
)
}
export default PlantManager
下面是子组件:UseCallbackSub
import React, { useEffect, memo } from 'react'
import { View, Text, Button } from '@tarojs/components'
import styles from './plant.module.scss'
interface StrawberryProps {
color: string
size: number
increment: ()=>void
changeSubjectNum: ()=>void
}
// 定义一个子组件,用于查看子组件的更新状态
const Strawberry: React.FC<StrawberryProps> = (props)=>{
const { increment, changeSubjectNum } = props;
useEffect(()=>{
console.log("子组件重新渲染更新!!!")
console.log("当前草莓的颜色: ", props.color);
})
return (
<View className={styles.berryBox}>
<Text className={styles.showText}>子组件 草莓</Text>
<Text className={styles.berryTxt} style={{
backgroundColor: props.color,
}}>
草莓的颜色
</Text>
<Text className={styles.berryTxt}>草莓的大小size = {props.size}</Text>
<View className={styles.showTextBox}>
<Text className={styles.showText}>普通函数,没有使用useCallback缓存, 每次都会重新创建</Text>
<Button className={styles.actionBox} onClick={increment}>增加草本植物的数量</Button>
</View>
<View className={styles.showTextBox}>
<Text className={styles.showText}>使用useCallback缓存函数</Text>
<Button className={styles.actionBox} onClick={changeSubjectNum}>增加草本植物的数量</Button>
</View>
</View>
)
}
// export default Strawberry;
export default memo(Strawberry);
下面的文件是样式:
.container {
margin-top: 30px;
margin-bottom: 50px;
margin-left: 15px;
margin-right: 15px;
padding: 20px;
background-color: darkgrey;
}
.scroll {
margin-top: 30px;
margin-bottom: 30px;
margin-left: 15px;
margin-right: 15px;
}
.showTextBox {
background-color: antiquewhite;
padding: 10px;
text-align: center;
margin-bottom: 30px;
}
.showText {
padding: 20px;
text-align: center;
margin-top: 18px;
background-color: cornflowerblue;
}
.actionBox {
background-color: aquamarine;
padding: 20px;
margin-top: 20px;
}
.actionContainer {
padding: 15px;
margin-top: 30px;
}
.berryBox {
background-color: darksalmon;
padding: 30px;
}
.berryTxt {
padding-left: 80px;
padding-right: 80px;
padding-top: 30px;
padding-bottom: 30px;
margin-bottom: 35px;
}