在《React Hooks 学习笔记 | State Hook(一)》和 《React Hooks 学习笔记 | useEffect Hook(二)》这两篇文章里我们分别学习了 State Hook 和 useEffect Hook,从本篇文章起,我们将讨论下如何应用 Hook 其他的函数提升组件的性能。
在 React 应用中,提升组件的性能涉及两个方面,一是减少不必要的渲染,二是减少渲染的时间。React 自身提供了一些可以非必要渲染的工具函数:memo、useMemo 和 useCallback。本篇文章将介绍下如何使用 React.memo。
React.memo 函数用于创建纯组件,对于给定的参数,纯函数始终返回相同的结果,纯组件与此相同,对于给定的属性,纯组件始终渲染相同的输出。换句话说,在传给组件的 props 的属性和值没有发生改 变的情况下,它会使用最近一次缓存的结果,而不会进行重新的渲染,实现跳过组件渲染的效果。
接下来,我们来看一个案例,来分析下 React.memo 存在的必要性和应用场景。
如下图所示,一个通过接口加载数据的产品列表,列表上方有个计数器,点击按钮计数器就+1,如下图所示:
页面中共两个子组件,产品列表 BigList
和 SingleProduct
这两个,SingleProduct
嵌套在 BigList
内部,示例代码如下:
SingleProduct
组件:
const SingleProduct = ({ fields }) => { 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> </article> ) }
BigList
组件:
const BigList = memo(({ products }) => { return ( <section className='products'> {products.map((product) => { return <SingleProduct key={product.id} {...product}></SingleProduct> })} </section> ); });
接下来我们来定义 useFetch
函数,用来获取数据,示例代码如下:
import { useState, useEffect, useCallback } from 'react'; export const useFetch = (url) => { const [loading, setLoading] = useState(true); const [products, setProducts] = useState([]); const getProducts = useCallback(async () => { const response = await fetch(url); const products = await response.json(); setProducts(products); setLoading(false); }, [url]); useEffect(() => { getProducts(); }, [url, getProducts]); return { loading, products }; };
接下来我们继续,引入 useFetch
函数,通过接口地址获取数据,示例代码如下:
const { products } = useFetch(url)
然后我们继续来定义 Index
将数据渲染到子组件中,并定义 count
数据状态,通过按钮点击,将其加1,示 例代码如下:
const url = 'https://course-api.com/javascript-store-products' const Index = () => { const { products } = useFetch(url) const [count, setCount] = useState(0) return ( <> <h1>Count : {count}</h1> <button className='btn' onClick={() => setCount(count + 1)}> click me </button> <BigList products={products} /> </> ) }
最后我们在 BigList
和 SingleProduct
函数里分别定义 useEffect
函数,来观察,点击计数器按钮,查看两个组件是否重新渲染:
// 在 BigList 组件中添加 useEffect(()=>{ console.log('产品列表开始加载'); }) // 在 SingleProduct 组件中添加 useEffect(()=>{ console.log('单个产品图加载'); })
到这里我们的代码部分就完了,接下来我们来观察下,点击计数器按钮,控制台会如何输出,如下图所示:
从上图我们可以看出,每次 count
的数据状态发生变化,都会导致页面的重新渲染,因此页面相关的组件都会重新渲染加载,因此你会看到组件对应的输出。那该如何解决呢,毕竟接口中的 products
数据没有发生变化,真的没必要重新渲染产品列表和产品图片组件,这时使用 React.memo
则是一个很好的解决方案。
使用 React.memo
十分简单,只需要在组件的最外层调用即可,组件的属性作为参数即可,如果参数不发生变化,组件将不会重新加载,否则将会重新加载,示例代码如下:
const BigList = React.memo(({ products }) => { useEffect(()=>{ console.log('产品列表开始加载'); }) return ( <section className='products'> {products.map((product) => { return <SingleProduct key={product.id} {...product}></SingleProduct> })} </section> ); });
最后我们来验证下,我们来点击计数器的按钮,看看控制台有没有组件重新渲染的输出,效果如下图所示:
今天的文章分享就到这里,感谢你的阅读。最后我们来做下React.memo
使用总结,希望对你有帮助:
React.memo
函数保护的子组件将会重新渲染React.memo
会检测 props 属性的改变,来决定组件是否需要进行重新渲染,换言之就是,被React.memo
函数包起来的组件只有本身的 props 被改变之后才会重新渲染。React.memo
不是项目中所有的组件都需要缓存。使用的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件进行有选择性的去缓存。注:本文属于原创文章,版权属于「前端达人」公众号及 qianduandaren.com 所有,未经授权,谢绝一切形式的转载