Hook使用规则
- 只能在函数的最外层调用Hook,不能在循环、条件判断或子函数中调用。
- 只能在React函数组件或自定义Hook中调用Hook,不可在其他JavaScript函数中使用。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
memo是memoized的简写,意思是缓存;computeExpensiveValue的意思是“计算开销很大的值”。
useMemo的作用是通过存储计算开销很大的值并在再次出现相同输入时返回缓存的结果,以此来加速计算机程序运行效率。
参数
参数1:纯函数
该参数必须是纯函数,即入参相同时输出结果必定相同,该纯函数必须有返回值。
常见的应用场景:例如根据id查名称,在成千上万的id中找对应名字的计算开销很大,非常适合用useMemo做性能优化。
参数2:计算依赖项
即参数1函数所需要的参数。
注意:当计算函数引用依赖项以外的变量时,由于Reac做t异步批量更新可能会造成引用到的值不是最新的。
返回值:计算开销很大的值
usuCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
callback的意思是回调函数;memoizedCallback的意思是缓存的回调函数。
usuCallback的作用是通过存储回调函数,使得该函数在入参不变时保持不变,可以避免重复渲染,优化性能。
参数
参数1:回调纯函数
该函数必须是纯函数,即入参相同输出的结果必定相同。
常见的应用场景:缓存更新数据的函数。
参数2:计算依赖项
即doSomething函数需要的参数。
注意:当计算函数引用依赖项以外的变量时,由于Reac做t异步批量更新可能会造成引用到的值不是最新的。
返回值:回调函数
补充
useCallback常用来和memo配合使用,光用useCallback不搭配memo不能阻止重复渲染。
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* 使用 props 渲染 */
});
React.memo是高阶组件,memo会检查参数1中的props变更情况及参数2中的设置来决定组件是否重新渲染。
组件内使用useState、useReducer、useContext等Hook发生变化而引起重新渲染不受memo影响。
参数1:函数组件
参数2:可选
- 无参数2时:对前后props作浅比较,2个对象内存地址相同就不重新渲染,不同就重新渲染。
- 有参数2时:参数2必须返回true或false,返回true不重新渲染;返回false重新渲染。
参数2范例:(preProps, nextProps) => preProps.id === nextProps.id
补充
memo参数2的判断逻辑与shouldComponentUpdate()生命周期方法不一样,shouldComponentUpdate()返回true表示可以重新渲染,返回false表示不可重新渲染。
案例:
import React, { memo, useCallback, useMemo, useState } from "react";
const Increase: React.FC<{ add: () => void }> = memo((props) => {
console.log("Increase")
return (
<button onClick={props.add}> + </button>
)
})
const Decrease: React.FC<{ sub: () => void }> = (props) => {
console.log("Decrease")
return (
<button onClick={props.sub}> - </button>
)
}
const Order: React.FC<{
item: {
id: number,
goodsId: number,
height: number,
userName: string,
},
goodsArr: { id: number, name: string }[],
setOrderList: React.Dispatch<React.SetStateAction<{
id: number;
goodsId: number;
height: number;
userName: string;
}>>,
}> = (props) => {
const { item, goodsArr, setOrderList } = props
const showName = useMemo(
() => {
console.log("计算了商品名")
for (const i of goodsArr) {
if (i.id === item.goodsId) {
return i.name
}
}
return ""
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[item.goodsId] // 正确的依赖项
// [item] // 错误的依赖项
)
const add = useCallback(() => setOrderList(preState => ({ ...preState, height: preState.height + 1 })), [])
const sub = useCallback(() => setOrderList(preState => ({ ...preState, height: preState.height > 0 ? preState.height - 1 : 0 })), [])
return (
<div>购买人:{item.userName},品名:{showName},重量:{item.height}斤 <Increase add={add} /> <Decrease sub={sub} /></div>
)
}
const App = () => {
const [orderList, setOrderList] = useState({ id: 1, goodsId: 1, height: 2, userName: "张三" })
const goodsArr = [
{ id: 1, name: "香蕉", },
{ id: 2, name: "苹果", },
{ id: 3, name: "桃子", },
{ id: 4, name: "葡萄", },
{ id: 5, name: "樱桃", },
]
return (
<div>
<Order item={orderList} goodsArr={goodsArr} setOrderList={setOrderList} />
</div>
)
}
export default App
代码表现如下:
在线体验
讲解
useMemo
注意Order组件中showName用到了useMemo,这里存储了商品名称,假设商品列表有上万条记录,那每一次根据id找name都是巨大的开销,如果不做优化,那么点击 + 或 - 按钮响应速度会很慢,给用户的体验就是卡顿。做了优化以后,点击 + 或 - 按钮showName中的console.log(“计算了商品名”)没有打印日志。
使用useMemo的难点在于第二个参数,要设置正确,上面代码注释了一条错误的依赖项,有兴趣的可以看看注释正确的依赖项打开错误的依赖项时运行的日志。
useCallback
注意看代码中的Increase和Decrease组件,这2个组件对应的按钮都绑定类对state做更新的回调函数。
memo
注意看上图中的日志,无论点击了增加按钮或减少按钮,Decrease组件都会重新渲染。差别就是Decrease组件没有使用memo。