大家好,在上一篇文章里《「React Hooks 学习笔记」关于 React.memo 介绍(三)》我们学习了使用 React.memo 进行缓存组件,减少组件的渲染次数,本篇文章我们继续学习 useMemo
和 useCallback
这两个函数,这两个函数也是起到缓存的作用,最大的区别是 useMemo
缓存的是值,useCallback
缓存的是函数。
接下来我们通过实例来分析下需求场景,接着上一篇文章里《「React Hooks 学习笔记」关于 React.memo 介绍(三)》的例子,我们在每个产品图片下方添加个添加到购物车的按钮,点击添加按钮,购物车的数量 + 1,具体的页面效果下图。
这里我们需要用到 useCallback
,接下来我们完成此部分功能
useCallback
首先我们来修改 SingleProduct
单个产品展示组件,将 addToCart
事件属性及按钮添加至组件,修改后的代码如下所示:
const SingleProduct = ({fields,addToCart}) => { useEffect(() => { console.log('单个产品图加载'); }) let {name, price} = fields price = price / 100 const image = fields.image[0].url return ( <article className='product'> <img src={image} alt={name}/> <h4>{name}</h4> <p>${price}</p> <button onClick={addToCart}>addToCart</button> </article> )
接下来我们需要修改 BigList
产品列表组件,将 addToCart
属性传递至子组件 SingleProduct
单个产品展示组件,示例代码如下:
const BigList = React.memo(({products,addToCart}) => { useEffect(() => { console.log('产品列表开始加载'); }) return ( <section className='products'> {products.map((product) => { return <SingleProduct key={product.id} {...product} addToCart = {addToCart} ></SingleProduct> })} </section> ); });
接下来我们在页面里修改产品列表及添加 cart
这个数据状态,并在页面显示购物车的数量,示例代码如下:
const Index = () => { //...省略多行代码 const [cart, setCart] = useState(0); return ( <> //...省略多行代码 <h1 style={{ marginTop:'3rem' }}> cart: {cart} </h1> <BigList products={products} addToCart={addToCart}/> </> ) }
你可能注意上述代码,列表组件里定义了 addToCart
方法,如果我们定义一个普通的方法,示例代码如下:
const addToCart =() => { console.log("购物车被点击了"+cart) setCart(cart+1); }
到这里,功能虽然是完成了,我们来看看实际效果,虽然点击购物车的按钮,购物车的数量是 + 1了,但是最上方的计数器按钮(click me),每点一次,你会发现 `React.memo` 这个组件起不到缓存的作用了,这是因为第二个属性 addToCart
是一个函数,数据状态发生变化时,都会创建一个新函数,造成列表组件及其子组件的重新渲染,效果如下:
为了解决这个问题,我们需要将函数缓存起来,这时我们就需要用到 useCallback
了,我们来改造下 addToCart
函数,示例代码如下:
const addToCart =useCallback(() => { console.log("购物车被点击了"+cart) setCart(cart+1); },[cart])
重新运行下项目,你在点击计数器按钮,BigList
产品列表组件和 SingleProduct
单个产品展示组件将不会重新渲染。
这时我们来观察下 useCallback
的第二个参数,这里是 cart
,类似 useEffect
的第二个参数,如果 cart
内容不变,直接从缓存里读取函数,否则重 新运行新函数。
你还可以尝试下去掉 cart
,猜猜控制台会如何输出。示例代码如下
const addToCart =useCallback(() => { console.log("购物车被点击了"+cart) setCart(cart+1); },[])
你会发现此函数只缓存了最初的函数副本,cart
的初始值始终是0,每次点击添加购物车按钮,始终输出 购物车被点击了0 。
useMemo
接下来我们来聊一聊如何使用 useMemo
,我们现在有个需求,需要在界面上显示当前产品中最高的单价,我们可以使用数组的 reduce
函数,我们先定义 calculateMostExpensive
函数,示例代码如下:
const calculateMostExpensive = (data) =>{ console.log("开始计算最高单的品的价格"); return( data.reduce((total,item) => { const price = item.fields.price if(price>=total){ total = price } return total },0)/100 ) }
然后在 Index
函数里调用函数,如下所示:
<h1>Most Expensive : ${calculateMostExpensive(products)}</h1>
接下来我们运行项目,你会发现,每当我们点击计数器按钮时(click me 按钮),都会重新运行我们的 calculateMostExpensive
函数,效果如下所示:
虽然界面感官上没啥变化,但是每次操作计数器,都会有每次都运行控制台都会产生“开始计算最高单的品的价格”的内容输出。 数据量小还行,数据量大真是一笔不小的系统开销(迭代寻找最大数)。由于我们的产品数据源没有发生变化,实在没有必要,这时就是使用 useMemo
的时机了。如下所示,我们在 Index
函数里定义 mostExpensive
函数:
const mostExpensive = useMemo( () => calculateMostExpensive(products), [products]);
你会看到有两个参数,第二参数是我们需要依赖的值,和 useCallback
和 useEffect
一样,这个参数发生变化时,重新运行。
useFetch
的应用在上一篇文章,就有关于 useFetch
的示例,没有过多的介绍,这是自定义 HOOKS 的运用(稍后的文章将会介绍),在这里我们使用了 useCallback
和 useEffect
。结合本篇文章的介绍,我们再来熟悉下加深下理解,示例代码如下:
import{useState,useEffect,useCallback}from'react'; export constuseFetch= (url) => { const[loading, setLoading] =useState(true); const[products, setProducts] =useState([]); constgetProducts =useCallback(async() => { constresponse =await fetch(url); constproducts =await response.json(); setProducts(products); setLoading(false); }, [url]); useEffect(() => { getProducts(); }, [url, getProducts]); return{ loading, products }; };
从上述代码我们看出,使用 useCallback
将接口请求函数缓存,如果 URL 发生变化,再重新触发请求。
到这里关于 useMemo
和 useCallback
的介绍就完了,大家是否有了清晰的认识了呢?最后我们来做个总结加深下印象吧。
在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用
useMemo
缓存的结果是回调函数中 return 回来的值,主要用于缓存计算结果的值,应用场景如需要计算的相关状态值。
useCallback
缓存的是函数,应用场景如需要缓存的函数,因为函数组件每次任何一个state发生变化,会触发整个组件更新,一些没必要更新的函数,此时就应该缓存起来,提高性能,减少系统资源的开销。
注:本文属于原创文章,版权属于「前端达人」公众号及 qianduandaren.com 所有,未经授权,谢绝一切形式的转载