React’s useMemo Hook to memoize a functions return value(s) and to run a function only if its dependencies have changed
基本使用
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。
示例1
import React from "react";
const users = [{ id: "a", name: "Robin" }, { id: "b", name: "Dennis" }];
const List = ({ list }) => {
return (
<ul>
{list.map(item => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
};
const ListItem = ({ item }) => {
return <li>{item.name}</li>;
};
const App = () => {
const [text, setText] = React.useState("");
const [search, setSearch] = React.useState("");
const handleText = event => {
setText(event.target.value);
};
const handleSearch = () => {
setSearch(text);
};
const filteredUsers = users.filter(user => {
console.log("Filter function is running ...");
return user.name.toLowerCase().includes(search.toLowerCase());
});
return (
<div>
<input type="text" value={text} onChange={handleText} />
<button type="button" onClick={handleSearch}>
Search
</button>
<List list={filteredUsers} />
</div>
);
};
export default App;
上面的例子中,在input中输入的时候,App的状态text会改变,App就会重新渲染,重新define filteredUsers,就会打印 Filter function is running …
可以看到只有users变化的时候,才会触发filteredUsers
示例2
假设以下场景,父组件在调用子组件时传递 info 对象属性,点击父组件按钮时,发现控制台会打印出子组件被渲染的信息。
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = { name, age };
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
分析原因:
点击父组件按钮,触发父组件重新渲染;父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。
解决:
使用 useMemo 将对象属性包一层,useMemo 有两个参数:
- 第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
- 第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象。
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ name, age }), [name, age]);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
完整代码见https://stackblitz.com/edit/react-kkxeqs
使用场景
- 优化函数返回值
Reference:
https://www.robinwieruch.de/react-usememo-hook
https://mp.weixin.qq.com/s/fF-H3Lr0aP3Ld8jfJrrwQg