| 背景
useMemo是用来缓存数据的重要手段,合理使用可以在一定程度上的优化组件的性能。
每个
useMemo是天然的闭包 ,而闭包内存消耗大,不合理的使用会导致内存泄露(占着茅坑不拉屎),变成负优化。
因此使用useMemo,是需要考量的。
| 什么时候使用
useMemo的存在有两个原因:
- 引用类型
- 高开销的计算
高开销的计算就不用说了,说一下引用类型
引用类型
先来看看一个demo
import React, { useState, useEffect } from 'react';
// 父组件
const Father = () => {
const [num, setNum] = useState(0);
const baz = 3;
const bar = 'bar value';
console.log('父页面渲染');
return (
<div>
// 触发re-render
<button
onClick={() => {
setNum(num => (num += 1));
}}
>
num++
</button>
<div>num:{num}</div>
<Son bar={bar} baz={baz} />
</div>
);
};
// 子组件
function Son({ bar, baz }) {
const options = { bar, baz };
useEffect(() => {
console.log(options, '子 useEffect hook执行了');
}, [options]); // 如果bar或 baz变化时就执行
return <div>我是子组件</div>;
}
可以看到,即使bar, baz不改变,子组件的useEffect回调还是执行了
options 每次渲染被重新赋值导致引用地址改变,因此
引用地址改变 ==> 依赖改变
意味着每次渲染都会调用子组件的useEffect回调
我们可以换种写法解决这个问题:
// 子组件
function Son({ bar, baz }) {
useEffect(() => {
const options = { bar, baz }; // 1.options定义在 useEffect里
console.log(options, '子 useEffect hook执行了');
}, [bar, baz]); // 2. 监听 bar 和 baz是否变化
return <div>我是子组件</div>;
}
如果baz和bar是引用类型呢?
const Father = () => {
const [num, setNum] = useState(0);
const bar = { name: 'tz', age: 18 };
const baz = [1, 2, 3];
console.log('父页面渲染');
return (
...
);
};
显然不起作用了
这时候useMeme派上用场了
const Father = () => {
const [num, setNum] = useState(0);
// 使用useMemo
const bar = useMemo(() => ({ name: 'tz', age: 18 }), []);
const baz = useMemo(() => [1, 2, 3], []);
console.log('父页面渲染');
return (
...
);
};
因此,使用useMemo的时候要考虑是否存在引用类型 or 高开销的计算
具体应用
- 依赖其他hook的引用类型
import React, {useState, useMemo} from "react";
const App = () => {
const [someDatas, setSomeDatas] = useState([1, 2, 3]);
// 依赖了其他 hook
const memoData = useMemo(
() =>
someDatas.map(item => {
return item + 100;
}),
[someDatas]
);
return <div>count: {memoData[0]}</div>;
};
- 父子组件传引用类型值
import React, { useState, useMemo, useEffect, memo } from 'react';
// 父组件
const Father = () => {
const bar = useMemo(() => ({ name: 'tz', age: 18 }), []);
const baz = useMemo(() => [1, 2, 3], []);
console.log('父页面渲染');
return (
<div>
<div>我是父组件</div>;
// 父给子传递了引用类型
<Son bar={bar} baz={baz} />
</div>
);
};
// 子组件
const Son = memo(({ bar, baz }) => {
console.log('子页面渲染');
useEffect(() => {
const options = { bar, baz };
console.log(options, '子 useEffect hook执行了');
}, [bar, baz]);
return <div>我是子组件</div>;
});
- 多次高开销的计算
import React, {useMemo} from "react";
const App = () => {
const ExpensiveCompute = useMemo(() => {
let sum = 0;
// 高开销运算
for (let i = 0; i < 100000; i++) {
sum += i;
}
return sum;
}, []);
return <div>count: {ExpensiveCompute}</div>;
};
高开销计算时useMemo使用对比
注:
第一个timer是开始计算时间(console.time)
第二个timer是脚本执行的时间(console.timeEnd)
使用useMemo
不使用
useMemo
可见使用useMemo后执行时间大幅下降
| 总结
useMemo本质上是一个闭包- 使用时考虑引用类型 or 昂贵计算
- 依赖其他hook的引用类型时使用
useMemo - 父子组件传引用类型值时使用
useMemo - 高开销计算时使用
useMemo
| 参考
# react hooks: 什么时候该用 useMemo / useCallback
# When to useMemo and useCallback