本系列将讲述 React Hooks 的使用方法,从 useState 开始,将包含如下内容:
掌握 React Hooks api 将更好的帮助你在工作中使用,对 React 的掌握更上一层楼。本系列将使用大量实例代码和效果展示,非常易于初学者和复习使用。
上一章,我们学习了 useCallback 来进行性能优化,关于性能优化还有另一个 hook api,那就是 useMemo,下面我们一起通过一个例子来看看。
依然是计数器示例,创建2个计数器,并能区分当前是奇数或者偶数,为了模拟点击按钮时包含大量的计算逻辑影响性能,在判断偶数的方法中添加了没有用的计算逻辑,为了让性能差的明显。代码如下
Counter.tsx
import React, { useState } from 'react' function Counter() { const [counterOne, setCounterOne] = useState(0) const [counterTwo, setCounterTwo] = useState(0) const incrementOne = () => { setCounterOne(counterOne + 1) } const incrementTwo = () => { setCounterTwo(counterTwo + 1) } const isEven = () => { let i = 0 while (i < 1000000000) i += 1 return counterOne % 2 === 0 } return ( <div> <button onClick={incrementOne} >Count One = {counterOne}</button> <span> { isEven() ? 'even' : 'odd' } </span> <br /> <button onClick={incrementTwo} >Count Two = {counterTwo}</button> </div> ) } export default Counter 复制代码
App.tsx
import React from 'react' import './App.css' import Counter from './components/27.Counter' const App = () => { return ( <div className="App"> <Counter /> </div> ) } export default App 复制代码
页面展示如下
我们发现点击第一个按钮有较长的延迟,因为我们的判断偶数的逻辑中包含了大量的计算逻辑。但是,我们点击第二个按钮,也有较长的延迟!这很奇怪。
这是因为,每次 state 更新时,组件会 rerender,isEven 会被执行,这就是我们点击第二个按钮时,也会卡的原因。我们需要优化,告诉 React 不要有不必要的计算,特别是这种计算量复杂的。
在我们的示例中,我们要告诉 React,在点击第二个按钮时,不要执行 isEven 方法。这时就需要 useMemo hook 登场了。
与 useCallback 的用法类似。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 复制代码
返回一个 memoized 值。 把“创建”函数和依赖项数组作为参数传入
useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。记住,传入
useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect
的适用范畴,而不是useMemo
。如果没有提供依赖项数组,
useMemo
在每次渲染时都会计算新的值。你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。 将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。
首先引入 useMemo
import React, { useState, useMemo } from 'react' 复制代码
然后将 isEven 方法使用 useMemo 改写,返回值赋给 isEven
const isEven = useMemo(() => { let i = 0 while (i < 1000000000) i += 1 return counterOne % 2 === 0 }, [counterOne]) 复制代码
最后记得修改 isEven 使用的地方,已经从一个方法变为了一个变量
{ isEven ? 'even' : 'odd' } 复制代码
完整代码如下
Counter.tsx
import React, { useState, useMemo } from 'react' function Counter() { const [counterOne, setCounterOne] = useState(0) const [counterTwo, setCounterTwo] = useState(0) const incrementOne = () => { setCounterOne(counterOne + 1) } const incrementTwo = () => { setCounterTwo(counterTwo + 1) } const isEven = useMemo(() => { let i = 0 while (i < 1000000000) i += 1 return counterOne % 2 === 0 }, [counterOne]) return ( <div> <button onClick={incrementOne} >Count One = {counterOne}</button> <span> { isEven ? 'even' : 'odd' } </span> <br /> <button onClick={incrementTwo} >Count Two = {counterTwo}</button> </div> ) } export default Counter 复制代码
效果如下
我们看到点击第二个按钮时,不会有任何卡顿,这是因为使用了 useMemo 只依赖了 counterOne 变量,点击第二个按钮时,isEven 读取的是缓存值,不需要再重新计算是否为偶数。
useCallback 是缓存了函数自身,而 useMemo 是缓存了函数的返回值。
本章通过示例展示了 useMemo 在性能优化中的作用。通过缓存函数的返回值,避免不必要的调用,从而避免了组件 rerender。
最后有分析了 useMemo 与 useCallback 的区别,即 useMemo 是缓存了函数的返回值,useCallback 是缓存了函数自身。这两个 api 都是性能优化的方法。