React’s useMemo Hook to memoize a functions return value(s) and to run a function only if its dependencies have changed

基本使用

  1. const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

示例1

  1. import React from "react";
  2. const users = [{ id: "a", name: "Robin" }, { id: "b", name: "Dennis" }];
  3. const List = ({ list }) => {
  4. return (
  5. <ul>
  6. {list.map(item => (
  7. <ListItem key={item.id} item={item} />
  8. ))}
  9. </ul>
  10. );
  11. };
  12. const ListItem = ({ item }) => {
  13. return <li>{item.name}</li>;
  14. };
  15. const App = () => {
  16. const [text, setText] = React.useState("");
  17. const [search, setSearch] = React.useState("");
  18. const handleText = event => {
  19. setText(event.target.value);
  20. };
  21. const handleSearch = () => {
  22. setSearch(text);
  23. };
  24. const filteredUsers = users.filter(user => {
  25. console.log("Filter function is running ...");
  26. return user.name.toLowerCase().includes(search.toLowerCase());
  27. });
  28. return (
  29. <div>
  30. <input type="text" value={text} onChange={handleText} />
  31. <button type="button" onClick={handleSearch}>
  32. Search
  33. </button>
  34. <List list={filteredUsers} />
  35. </div>
  36. );
  37. };
  38. export default App;

上面的例子中,在input中输入的时候,App的状态text会改变,App就会重新渲染,重新define filteredUsers,就会打印 Filter function is running …

image.png
可以看到只有users变化的时候,才会触发filteredUsers

示例2

假设以下场景,父组件在调用子组件时传递 info 对象属性,点击父组件按钮时,发现控制台会打印出子组件被渲染的信息。

  1. import React, { memo, useState } from 'react';
  2. // 子组件
  3. const ChildComp = (info:{info:{name: string, age: number}}) => {
  4. console.log('ChildComp...');
  5. return (<div>ChildComp...</div>);
  6. };
  7. const MemoChildComp = memo(ChildComp);
  8. // 父组件
  9. const Parent = () => {
  10. const [count, setCount] = useState(0);
  11. const [name] = useState('jack');
  12. const [age] = useState(11);
  13. const info = { name, age };
  14. return (
  15. <div className="App">
  16. <div>hello world {count}</div>
  17. <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
  18. <MemoChildComp info={info}/>
  19. </div>
  20. );
  21. };
  22. export default Parent;

分析原因:

点击父组件按钮,触发父组件重新渲染;父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。

解决:

使用 useMemo 将对象属性包一层,useMemo 有两个参数:

  • 第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
  • 第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象。
  1. import React, { memo, useMemo, useState } from 'react';
  2. // 子组件
  3. const ChildComp = (info:{info:{name: string, age: number}}) => {
  4. console.log('ChildComp...');
  5. return (<div>ChildComp...</div>);
  6. };
  7. const MemoChildComp = memo(ChildComp);
  8. // 父组件
  9. const Parent = () => {
  10. const [count, setCount] = useState(0);
  11. const [name] = useState('jack');
  12. const [age] = useState(11);
  13. // 使用 useMemo 将对象属性包一层
  14. const info = useMemo(() => ({ name, age }), [name, age]);
  15. return (
  16. <div className="App">
  17. <div>hello world {count}</div>
  18. <div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
  19. <MemoChildComp info={info}/>
  20. </div>
  21. );
  22. };
  23. 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