useMemo 和 memo 函数
1. memo 函数
当父组件被重新渲染的时候,也会触发子组件的重新渲染,这样就多出了无意义的性能开销。如果子组件的状态没有发生变化,则子组件是必须要被重新渲染的。
在 React 中,我们可以使用 React.memo()
函数来解决上述的问题,从而达到提高性能的目的。
React.memo()
的语法格式如下:
const 组件 = React.memo(函数式组件)
例如,在下面的代码中,父组件声明了 count 和 flag 两个状态,子组件依赖于父组件通过 props 传递的 num。当父组件修改 flag 的值时,会导致子组件的重新渲染:
import React, { useEffect, useState } from 'react'// 父组件
export const Father: React.FC = () => {// 定义 count 和 flag 两个状态const [count, setCount] = useState(0)const [flag, setFlag] = useState(false)return (<><h1>父组件</h1><p>count 的值是:{count}</p><p>flag 的值是:{String(flag)}</p><button onClick={() => setCount((prev) => prev + 1)}>+1</button><button onClick={() => setFlag((prev) => !prev)}>Toggle</button><hr /><Son num={count} /></>)
}// 子组件:依赖于父组件通过 props 传递进来的 num
export const Son: React.FC<{ num: number }> = ({ num }) => {useEffect(() => {console.log('触发了子组件的渲染')})return (<><h3>子组件 {num}</h3></>)
}
【解决方案】我们使用 React.memo(函数式组件)
将子组件包裹起来,只有子组件依赖的 props
发生变化的时候,才会触发子组件的重新渲染。示例代码如下:
// 子组件:依赖于父组件通过 props 传递进来的 num
export const Son: React.FC<{ num: number }> = React.memo(({ num }) => {useEffect(() => {console.log('触发了子组件的渲染')})return (<><h3>子组件 --- {num}</h3></>)
})
2. useMemo - 问题引入
进一步改造前面的案例:我们希望在 Father 组件中添加一个“计算属性”,根据 flag 值的真假,动态返回一段文本内容,并把计算的结果显示到页面上。示例代码如下:
// 父组件
export const Father: React.FC = () => {// 定义 count 和 flag 两个状态const [count, setCount] = useState(0)const [flag, setFlag] = useState(false)// 根据布尔值进行计算,动态返回内容const tips = () => {console.log('触发了 tips 的重新计算') // 我们希望只有 flag 的变化 ,才会导致函数重新计算(结果)return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>}return (<><h1>父组件</h1><p>count 的值是:{count}</p><p>flag 的值是:{String(flag)}</p>// 通过函数执行触发动态渲染{tips()}<button onClick={() => setCount((prev) => prev + 1)}>+1</button><button onClick={() => setFlag((prev) => !prev)}>Toggle</button><hr /><Son num={count} /></>)
}
代码编写完毕后,我们点击父组件中的 +1 按钮,发现 count 在自增,而 flag 的值不会发生变化。此时也会触发 tips 函数的重新执行,这就造成了性能的浪费。
我们希望如果 flag 没有发生变化,则避免 tips 函数的重新计算,从而优化性能。此时需要用到 React Hooks 提供的 useMemo API。
3. useMemo - 语法格式
useMemo
的作用是对函数计算结果进行缓存,返回值是一个数值,语法格式如下:
const memorizedValue = useMemo(callback, array)// 【注意】 useMemo的返回值是一个值,使用时不需要加()
const memoValue = useMemo(() => {return 计算得到的值
}, [value]) // 表示监听 value 的变化
其中:
1. callback:这是一个函数,用户处理计算的逻辑,必须使用 return 返回计算的结果;
2. array:这个数组中存储的是依赖项,只有依赖项发生变化,都会触发 callback
的重新执行。
使用 array 需要注意以下3点:
1、不传数组,每次更新都会重新计算
2、空数组,只会计算一次
3、依赖对应的值,对应的值发生变化时会重新执行
callback
4. useMemo - 使用 useMemo 解决刚才的问题
导入 useMemo:
import React, { useEffect, useState, useMemo } from 'react'
在 Father 组件中,使用 useMemo 对 tips 进行改造:
// 根据布尔值进行计算,动态返回内容
const tips = useMemo(() => {console.log('触发了 tips 的重新计算')return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>
}, [flag])return (<>// 此时这里直接写变量名 tips{tips}</>)
此时,点击 Father 中的 +1 按钮,并不会触发 tips 的重新计算,而是会使用上一次缓存的值进行渲染。只有依赖项 flag 变化时,才会触发 tips 的重新计算。