useMemo
useMemo
是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
使用方式为
const cachedValue = useMemo(calculateValue, dependencies)
传入的参数第一个是对依赖值进行计算的函数。该函数没有任何参数,并且返回值是 useMemo
的缓存值 cachedValue 。
第二个参数是依赖值数组,里面存入的是在组件中,并且在函数calculateValue计算需要用到的React响应式变量。React 使用 Object.is
将每个依赖项与其之前的值进行比较。
useMemo
的返回值就是依赖项 dependencies 经过函数 calculateValue 计算的值。在初次渲染中,cachedValue 就是函数 calculateValue 计算的值。在接下来的渲染中,如果依赖项 dependencies 没有变化,cachedValue 就是上次缓存的值;如果依赖项 dependencies 发生变化,cachedValue 就会经过 calculateValue 重新计算得到。
注意事项:
useMemo
是一个 React Hook,所以你只能 在组件的顶层 或者自定义 Hook 中调用它。不能在循环语句或条件语句中调用它。
组件中的使用方式
import { useState, useMemo } from "react"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
const [count, setCount] = useState(0)
// 用 useMemo
const sumByCount = useMemo(() => factorialOf(count), [count])
}
缓存计算值
import { useState, useMemo } from "react"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
const [count, setCount] = useState(0)
// 计算斐波那契之和
const sumByCount = factorialOf(count)
const [num, setNum] = useState(0)
return (
<>
<div>{sumByCount}</div>
<div>
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</div>
</>
)
}
当触发setCount或setNum的时候,就会重新渲染,从而会重新执行 sumByCount 的计算。但是 sumByCount 只依赖于 count 。当触发 SetNum 的时候,sumByCount 就做了一次无效计算。这时候可以用 useMemo 将 sumByCount 的值缓存起来,当依赖项 count 发生变化的时候,再重新计算 sumByCount 的值。当 count 的值与上次渲染时相同,那么就可以重用之前计算过的 sumByCount 。
import { useState, useMemo } from "react"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sumByCount = factorialOf(count)
// 用 useMemo
const sumByCount = useMemo(() => factorialOf(count), [count])
const [num, setNum] = useState(0)
return (
<>
<div>{sumByCount}</div>
<div>
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</div>
</>
)
}
记忆另一个Hook的依赖项
import { useState, useMemo } from "react"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
...
// 记录另一个Hook的依赖
const searchItem = [
{label: 'apple', key: 'apple'},
{label: 'banana', key: 'banana'},
{label: 'orange', key: 'orange'},
]
const chosenItem = useMemo(() => {
console.log('计算chosenItem')
return searchItem[0].key
}, [searchItem])
return (
<>
<div>{sumByCount}</div>
<div>
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</div>
<div>
key: {chosenItem}
</div>
</>
)
}
现在 chosenItem 依赖于 searchItem 。但是React每次渲染就像一张快照,也就是说每次渲染函数执行的时候,函数都会拥有独立的state和props,函数内部的变量也是独立。所以每次渲染中 searchItem 的引用都是不同的。那么,在每次渲染中,useMemo都会判断 searchItem 发生了变化,从而重新执行 chosenItem 的计算。
为了避免无效的重新计算,可以将 searchItem 缓存起来,也可以通过 useRef 保存。不过当 searchItem 会依赖于组件中的props时候,建议采取 useMemo 进行缓存。
import { useState, useMemo } from "react"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
...
// 记录另一个Hook的依赖
// const searchItem = [
// {label: 'apple', key: 'apple'},
// {label: 'banana', key: 'banana'},
// {label: 'orange', key: 'orange'},
// ]
const searchItem = useMemo(() => {
return [
{label: 'apple', key: 'apple'},
{label: 'banana', key: 'banana'},
{label: 'orange', key: 'orange'},
]
}, [])
const chosenItem = useMemo(() => {
console.log('计算chosenItem')
return searchItem[0].key
}, [searchItem])
return (
<>
<div>{sumByCount}</div>
<div>
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</div>
<div>
key: {chosenItem}
</div>
</>
)
}
避免组件的重新渲染
import { useState, useMemo } from "react"
import List from "./components/List"
function factorialOf(n) {
console.log('斐波那契函数执行了')
return n <= 0 ? 1 : n * factorialOf(n - 1)
}
export default function UseMemoDemo () {
// useMemo缓存计算值
const [count, setCount] = useState(0)
// 计算斐波那契之和
// const sumByCount = factorialOf(count)
// 用 useMemo
const sumByCount = useMemo(() => factorialOf(count), [count])
const [num, setNum] = useState(0)
// 记录另一个Hook的依赖
// const searchItem = [
// {label: 'apple', key: 'apple'},
// {label: 'banana', key: 'banana'},
// {label: 'orange', key: 'orange'},
// ]
const searchItem = useMemo(() => {
return [
{label: 'apple', key: 'apple'},
{label: 'banana', key: 'banana'},
{label: 'orange', key: 'orange'},
]
}, [])
const chosenItem = useMemo(() => {
console.log('计算chosenItem')
return searchItem[0].key
}, [searchItem])
// 避免重新渲染
return (
<>
<div>{sumByCount}</div>
<div>
<button onClick={() => setCount(count + 1)}>+count:{count}</button>
<button onClick={() => setNum(num + 1)}>+num:{num}</button>
</div>
<div>
key: {chosenItem}
</div>
{/* 避免重新渲染 */}
<div>
<List searchList={searchItem}></List>
</div>
</>
)
}
// List.jsx
export default function List ({ searchList}) {
console.log('List组件渲染了')
return (
<ul>
{searchList.map(item => {
return <li key={item.key}>{item.label}</li>
})}
</ul>
)
}
现在 searchItem 作为props传递给子组件List。但是当我们触发 setCount 或 setNum 的时候,父组件重新渲染,子组件 List 也会跟着重新渲染。这是因为默认情况下,当一个组件重新渲染时,React 会递归地重新渲染它的所有子组件。当我们不希望子组件 List 在props不变的情况下跟着重新渲染,可以将子组件 List 包装在 memo
中,这样当它的 props 跟上一次渲染相同的时候它就会跳过本次渲染:
import { memo } from 'react'
const List = memo(function List ({ searchList}) {
console.log('List组件渲染了')
return (
<ul>
{searchList.map(item => {
return <li key={item.key}>{item.label}</li>
})}
</ul>
)
})
export default List
总结
- useMemo 可以将依赖项经过一些计算得到的值缓存起来,直到依赖项发生变化的时候才需要重新计算;
- useMemo 类似于vue中的计算属性 computed ;
- 用useMemo缓存的值需要传给子组件的时候,可以对子组件用 memo 包装,从而避免子组件的重新渲染;