React性能優(yōu)化之useMemo、useCallback
前言
在React應(yīng)用程序中進(jìn)行性能優(yōu)化至關(guān)重要,因為它直接影響用戶體驗,隨后影響轉(zhuǎn)化率和用戶留存率。一個經(jīng)過優(yōu)化的React應(yīng)用加載更快,響應(yīng)迅速,消耗更少的資源,從而為用戶提供更順暢的互動體驗。優(yōu)化React應(yīng)用程序的性能對于保留和吸引用戶、降低跳出率以及最終實現(xiàn)所期望的業(yè)務(wù)結(jié)果至關(guān)重要。
記憶化(Memoization)是改善React應(yīng)用程序性能的一種方式。記憶化涉及緩存值和函數(shù)以實現(xiàn)更快的渲染。
在本文中,我們將了解什么是記憶化以及如何使用useMemo和useCallback鉤子來實現(xiàn)記憶化。
理解React中的記憶化
記憶化是存儲計算結(jié)果的過程,以便在不重新計算的情況下檢索它。記憶化用于跳過組件的不必要重新渲染。
在React中,可以通過使用useMemo和useCallback鉤子來實現(xiàn)記憶化。這些鉤子允許你通過緩存函數(shù)調(diào)用和函數(shù)本身來跳過重新渲染。
使用useMemo入門
useMemo鉤子用于緩存函數(shù)調(diào)用。useMemo鉤子接受兩個參數(shù):
- 函數(shù)
- 依賴項數(shù)組
const doubledNumber = useMemo(() => {
return someExpensiveCalculation(number);
}, [number]);
useMemo鉤子在第一次渲染時返回調(diào)用函數(shù)的結(jié)果。在后續(xù)重新渲染中,useMemo鉤子將提供你的函數(shù)調(diào)用的緩存值,除非至少一個依賴項發(fā)生更改。
在JavaScript中,函數(shù)在代碼再次運行時會被重新創(chuàng)建。這意味著盡管函數(shù)的代碼相同,但每次應(yīng)用程序重新渲染時,你的函數(shù)并不相同。這是因為JavaScript對象和函數(shù)是引用對象。
useMemo鉤子旨在通過緩存調(diào)用函數(shù)的結(jié)果來消除函數(shù)的重新創(chuàng)建。在后續(xù)重新渲染中,useMemo鉤子會返回緩存的值,跳過函數(shù)的重新創(chuàng)建。
讓我們通過一個實際示例來了解如何在React中使用useMemo。
在這個示例中,我們正在對幾千篇博客文章進(jìn)行排序,并將每篇博客文章顯示在屏幕上。在每次重新渲染時對博客文章進(jìn)行排序可能會減慢我們的應(yīng)用程序。
使用useMemo鉤子來跳過博客文章的排序,如果文章列表沒有更改。
function PostList({ posts }) {
const sortedPosts = useMemo(() => {
return [...posts].sort((a, b) => a.date - b.date);
}, [posts]);
return (
<div>
<h2>已排序的文章</h2>
<ul>
{sortedPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
useMemo鉤子用于緩存sortedPosts。只有在posts的值更改時,sortedPosts才會重新計算。
通過這種方式,排序操作僅在文章數(shù)組更改時發(fā)生,從而跳過重新渲染并提高性能。
充分利用useCallback的能力
useCallback用于緩存函數(shù)聲明。它同樣接受兩個參數(shù):
- 函數(shù)
- 依賴項數(shù)組
與useMemo不同,useCallback不會調(diào)用你的函數(shù)。useCallback只是將函數(shù)返回給你。你可以自行決定何時以及是否調(diào)用函數(shù)。
useCallback鉤子的工作方式與useMemo鉤子類似。useCallback通過返回函數(shù)的緩存版本來消除函數(shù)的重新創(chuàng)建。只有在依賴項中的至少一個值發(fā)生更改時,才會重新創(chuàng)建你的函數(shù)。
讓我們看一個使用useCallback來優(yōu)化React應(yīng)用程序的示例。
在React組件中,當(dāng)單擊按鈕時,你有一個對產(chǎn)品數(shù)組進(jìn)行排序的函數(shù)。在每次重新渲染時,這個排序函數(shù)都會被重新創(chuàng)建。
使用useCallback鉤子來緩存sortPosts函數(shù)聲明,以及posts狀態(tài)作為一個依賴項。
import React, { useState, useCallback } from 'react';
function PostList() {
const [posts, setPosts] = useState([
{ id: 1, title: '文章1', date: new Date('2023-10-15T00:00:00.000Z') },
{ id: 2, title
: '文章2', date: new Date('2023-10-10T00:00:00.000Z') },
{ id: 3, title: '文章3', date: new Date('2023-10-20T00:00:00.000Z') },
]);
const sortPosts = useCallback(() => {
const sortedPosts = [...posts].sort((a, b) => a.date - b.date);
setPosts(sortedPosts);
}, [posts]);
return (
<div>
<button onClick={sortPosts}>排序文章</button>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
)}
</ul>
</div>
);
}
export default PostList;
現(xiàn)在,只有當(dāng)posts狀態(tài)更改時,sortPosts函數(shù)才會被重新創(chuàng)建,從而提高我們的React應(yīng)用程序性能。
通過結(jié)合使用useCallback和useMemo來優(yōu)化性能
useCallback和useMemo鉤子可以一起使用,以獲得顯著的性能提升。
React組件重新渲染依賴以下兩個條件:
- 狀態(tài)發(fā)生變化
- 屬性發(fā)生變化
通過useCallback鉤子,你可以緩存屬性(如果是一個函數(shù)),然后將其傳遞給包裝在useMemo中的子組件。
接下來看一個將useCallback與useMemo結(jié)合使用的示例。
假設(shè)你正在為網(wǎng)站開發(fā)產(chǎn)品列表頁面。用戶可以根據(jù)價格、品牌和類別等各種標(biāo)準(zhǔn)對產(chǎn)品進(jìn)行篩選和排序。
如果不使用useCallback和useMemo鉤子,你的ProductList組件將如下所示:
javascriptCopy code
import React, { useState } from 'react';
function ProductList({ products }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('price');
// 根據(jù)用戶輸入篩選產(chǎn)品
const filteredProducts = products.filter(product =>
product.title.toLowerCase().includes(filter.toLowerCase())
);
// 對篩選后的產(chǎn)品進(jìn)行排序
const sortedProducts = [...filteredProducts].sort((a, b) => {
if (sortBy === 'price') {
return a.price - b.price;
} else if sortBy === 'rating') {
return b.rating - a.rating;
}
// 在這里處理其他排序條件
});
// 處理篩選更改
const handleFilterChange = (event) => {
setFilter(event.target.value);
};
// 處理排序更改
const handleSortChange = (event) => {
setSortBy(event.target.value);
};
return (
<div>
<input
type="text"
placeholder="篩選產(chǎn)品"
value={filter}
onChange={handleFilterChange}
/>
<select value={sortBy} onChange={handleSortChange}>
<option value="price">價格</option>
<option value="rating">評分</option>
{/* 根據(jù)需要添加更多排序選項 */}
</select>
<ul>
{sortedProducts.map(product => (
<li key={product.id}>{product.title}</li>
)}
</ul>
</div>
);
}
export default ProductList;
雖然ProductList組件沒有問題,但它可能導(dǎo)致不必要的重新渲染和性能瓶頸。
如果ProductList處理大量產(chǎn)品,不必要的重新渲染會減慢你的電子商務(wù)站點。
使用useMemo和useCallback鉤子來減輕不必要的重新渲染并加速你的網(wǎng)站。
javascriptCopy code
import React, { useState, useMemo, useCallback } from 'react';
function ProductList({ products }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('price');
// 根據(jù)用戶輸入篩選產(chǎn)品
const filteredProducts = useMemo(() => {
return products.filter(product => product.title.toLowerCase().includes(filter.toLowerCase()));
}, [products, filter]);
// 對篩選后的產(chǎn)品進(jìn)行排序
const sortedProducts = useMemo(() => {
return [...filteredProducts].sort((a, b) => {
if (sortBy === 'price') {
return a.price - b.price;
} else if sortBy === 'rating') {
return b.rating - a.rating;
}
// 在這里處理其他排序條件
});
}, [filteredProducts, sortBy]);
// 記憶化回調(diào)來處理篩選更改
const handleFilterChange = useCallback((event) => {
setFilter(event.target.value);
}, []);
// 記憶化回調(diào)來處理排序更改
const handleSortChange = useCallback((event) => {
setSortBy(event.target.value);
}, []);
return (
<div>
<input
type="text"
placeholder="篩選產(chǎn)品"
value={filter}
onChange={handleFilterChange}
/>
<select value={sortBy} onChange={handleSortChange}>
<option value="price">價格</option>
<option value="rating">評分</option>
{/* 根據(jù)需要添加更多排序選項 */}
</select>
<ul>
{sortedProducts.map(product => (
<li key={product.id}>{product.title}</li>
)}
</ul>
</div>
);
}
export default ProductList;
在這個經(jīng)過優(yōu)化的ProductList組件版本中,useMemo用于優(yōu)化篩選和排序操作,而useCallback用于記憶化篩選和排序更改的事件處理程序函數(shù)。ProductList組件僅在products的值發(fā)生變化時重新渲染。
注意事項
盡管useMemo和useCallback是性能優(yōu)化鉤子,但開發(fā)人員往往會過度使用這些鉤子。有些情況下,使用useMemo和useCallback并不對你的應(yīng)用程序產(chǎn)生大的收益。由于現(xiàn)代計算機(jī)的快速性質(zhì),大多數(shù)操作都是廉價的。在操作廉價的情況下,你不需要useMemo和useCallback鉤子。例如,你有一個要排序的包含100個帖子的數(shù)組。使用useMemo或useCallback鉤子不會帶來任何性能提升,因為排序操作只需要幾毫秒。
總結(jié)
記憶化用于提高React應(yīng)用程序的性能,記憶化用于跳過組件的不必要重新渲染。useMemo和useCallback鉤子分別用于記憶化返回值和函數(shù)聲明。雖然useMemo和useCallback的行為類似,但兩者之間存在關(guān)鍵差異。useMemo返回調(diào)用函數(shù)的結(jié)果;useCallback返回函數(shù)本身。
useMemo和useCallback鉤子有助于優(yōu)化性能,但使用它們也可能帶來一些潛在問題,如過度優(yōu)化和增加代碼復(fù)雜性。