useMemo hooks 介绍
useMemo hooks 的语法如下
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
该 hooks 返回一个记忆过的值,也就是计算过的值
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
推荐启用 eslint-plugin-react-hooks
中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。
从上面可以看出,react useMemo hooks 可以用来优化函数组件的运行效率
不同于 useCallback , useMemo 是用来“记忆”值,useCallback 用来“记忆”函数,React.memo 用来包裹函数组件,避免重新渲染。
接下来通过实际例子来演示,比如我们有一个筛选列表,我们可以会这样实现
const Content = () => { const [keyword, setKeyword] = useState(""); const filterList = initList.filter((i) => { console.log("filter handle"); return new RegExp(keyword, "ig").test(i.name); }); const handleKeywordChange = (e) => { setKeyword(e.target.value); }; return ( <div> <div> <input value={keyword} type="text" placeholder="搜索" onChange={handleKeywordChange} /> </div> <ul> {filterList.map((i) => ( <li key={i.id}>{i.name}</li> ))} </ul> </div> );};
看起来功能没什么毛病吧,但是正常的功能页面不止一个状态,我们比如新增一个 loading 字段,这里例子当作只是用来演示多个状态如何影响性能
const Content = () => { const [loading, setLoading] = useState(false); const [keyword, setKeyword] = useState(""); const filterList = initList.filter((i) => { console.log("filter handle"); return new RegExp(keyword, "ig").test(i.name); }); const handleKeywordChange = (e) => { setKeyword(e.target.value); }; return ( <div> <div> <input value={keyword} type="text" placeholder="搜索" onChange={handleKeywordChange} /> <button onClick={() => { setLoading(!loading); }} > 搜索 </button> </div> <ul> {loading && <span>loading...</span>} {filterList.map((i) => ( <li key={i.id}>{i.name}</li> ))} </ul> </div> );};
可以看出,每次更新 loading 值,filter 计算都会重新被调用(通过 console 可以看出),这时候是不是就浪费了大量不必要都重新计算,这时候 useMemo 就可以派上用场了,只需要做下改动即可
const Content = () => { const [loading, setLoading] = useState(false); const [keyword, setKeyword] = useState(""); const filterList = useMemo(() => { return initList.filter((i) => { console.log("filter handle"); return new RegExp(keyword, "ig").test(i.name); }); }, [keyword]); const handleKeywordChange = (e) => { setKeyword(e.target.value); }; return ( <div> <div> <input value={keyword} type="text" placeholder="搜索" onChange={handleKeywordChange} /> <button onClick={() => { setLoading(!loading); }} > 搜索 </button> </div> <ul> {loading && <span>loading...</span>} {filterList.map((i) => ( <li key={i.id}>{i.name}</li> ))} </ul> </div> );};
这时候如果重新改变 loading 状态,我们可以看到 filter 不会重新计算了。是不是很 happy。
具体效果可以通过以下代码来演示