8 个 React 性能技巧:让你的应用运行速度飞快!
0. 设置
1. 避免使用索引作为键
2. useEffect() 和 useCallback()
3. 记忆 React 组件
4. React.Fragments
5. 延迟加载
6.渐进式图像加载
7. JS 动画代替 CSS 动画。
8. 生产构建
优化是每个开发人员最重要的方面之一,尤其是在构建 Web 应用时。通过使用虚拟 DOM,React 可以尽可能高效地更新 UI。
React 的工作原理
每个 React 应用程序都由许多组件组成,这些组件以树状结构排列。组件是根据接收的 props 渲染 UI 的函数。每当数据发生变化时,React 都会计算当前 UI 和新 UI 之间的差异,然后仅将 UI 更改应用于浏览器的实际 UI。反复比较和渲染组件可能是导致 React 性能问题的主要原因之一。
我们希望 React 仅重新渲染那些受其接收数据(state 或 props)变化影响的组件。在本文中,我将向您展示 8 种不同的技术来提升应用的整体性能。让我们开始吧!
- 避免使用索引作为键
- UseEffect() 和 UseCallback()
- React.Memo
- React.Fragments
- 延迟加载
- 渐进式图像
- JS 动画代替 CSS 动画
- 生产构建
0. 设置
让我们首先创建一个基本的 React 函数式组件,它使用 axios 从 API 获取数据,并将列表显示在 UI 上。我们的状态会跟踪加载、错误以及传入的数据。通过将 useEffect 与 useCallback 结合使用,我们可以确保 API 的获取操作不会在每次渲染时重复调用。
对于 API,我将随机选择一个有趣的公共 API,即 Cocktail 数据库 API。您可以在这里找到免费公共 API 列表。
创建一个新的 React 应用程序npx create-react-app perf-test
并加载上面的代码。
性能测量
我们将使用 Chrome 性能选项卡来测量 React 应用的性能,这也是React 建议的。请务必禁用所有 Chrome 扩展程序,尤其是 React DevTools。因为它们会显著影响结果。我还将 CPU 速度降低到 6 倍,以重现更大规模的数据和更慢的机器。
1. 避免使用索引作为键
我上面创建的示例获取了 25 种鸡尾酒配方的列表,并让用户可以将自己的配方添加到列表中。
当用户添加新的鸡尾酒时,addCocktail() 函数会更新鸡尾酒的状态钩子。使用 useRef() 函数,我们可以引用输入字段并确保它们不为空。
这个例子中的问题在于,每次添加新配方时,组件都会完全重新渲染。在 Chrome Devtools 中启用“Paint Flashing”后,你可以看到哪些 DOM 节点被更新了。
渲染时间:336毫秒
这是因为我们数组中的每杯鸡尾酒都向右移动了一个索引。一个很棒的改进方法是使用唯一 ID 来代替索引。您可以使用 npm 包 uuid 来生成唯一 ID。
...
const updatedCocktails = [
{
idDrink: uuidv4(),
strDrink: currentName,
strInstructions: currentDescription
}
].concat(cocktails);
...
cocktails.map((cocktail, index) => {
const { idDrink, strDrink, strInstructions } = cocktail;
return (
<div key={idDrink}>
<strong>{strDrink}</strong> - {strInstructions}
</div>
);
})
...
渲染时间:233毫秒
太棒了!我们继续吧。
2. useEffect() 和 useCallback()
我们使用 useEffect() hook 在组件挂载后立即获取混合数据。它只会在依赖项发生变化时重新运行(在本例中是 getCocktails 函数)。使用 useCallback() 可以确保在 App 组件每次重新渲染时无需再次获取 API 数据。
在我们的例子中,这不会产生很大的差异,但是当你有一个带有许多子组件的大型组件时,在这种情况下,当 getCocktails 改变父组件的状态或道具时,不完全重新渲染组件可能会产生很大的差异。
function App() {
const getCocktails = useCallback((query) => {
axios
.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?f=${query}`)
.then((response) => {
setCocktails(response.data.drinks);
setIsLoading(false);
})
.catch((error) => {
setErrors(error);
});
}, []);
useEffect(() => {
getCocktails("a");
}, [getCocktails]);
}
在上面的代码中,每当getCocktails
发生更改时,效果都会重新运行,以确保它具有 的最新版本getCocktails
。每次重建而不使用该函数时,该getCocktails
函数都会重新创建,并且当它从 更改状态或道具时,将调用无限循环。App
useCallback
App
useCallback
通过将其包装在函数声明中并定义函数的依赖关系,可以帮助您避免这种情况,它确保只有当依赖关系发生变化时才会重新创建该函数。因此,该函数不再会在每次渲染周期中重新构建。
3. 记忆 React 组件
React.Memo 是一个高阶组件 (HOC),它通过记忆结果来包装另一个组件,这意味着 React 将跳过渲染该组件,并重用上次渲染的结果。这可以提升你的应用性能。
我们可以将鸡尾酒 div 存储在其自己的无状态功能组件中,并用 React.Memo() 包装它。
// index.js
...
cocktails.map(({ idDrink, ...otherProps }) => (<Cocktail key={idDrink} {...otherProps} />))
...
// Cocktail.js
import React from "react";
const Cocktail = ({ strDrink, strInstructions }) => {
return (
<div>
<strong>{strDrink}</strong> - {strInstructions}
</div>
);
};
export default React.memo(Cocktail);
渲染时间:192毫秒
4. React.Fragments
在 React 中,一个组件内包含多个组件是很常见的。你总是需要将子组件包装到一个主组件中。使用 Fragments,你可以避免为主包装组件添加更多 DOM 节点。你可以使用<Fragment>
标签并从 React 中导入,或者使用空标签<></>
例子:
return (
<>
<h2>Cocktails</h2>
{!isLoading ? (
cocktails.map(({ idDrink, ...otherProps }) => (
<Cocktail key={idDrink} {...otherProps} />
))
) : (
<p>Loading...</p>
)}
</>
);
在我们的例子中,差异很小,但如果您有数十万个使用 div 的组件,则性能可能会有很大差异。
5. 延迟加载
React 的另一个原生方法是 React.lazy 函数,它将在当前组件渲染完成后立即加载请求的组件。
例如:
// Normal
import Home from '../screens/Home';
// Lazy
const Home = lazy(() => import("../screens/Home"));
必须在组件内调用惰性组件,<Suspense>
以便用户在组件加载时看到后备项。
<Suspense fallback={<Fragment>Loading...</Fragment>}>
<Switch>
<Route exact path="/" component={Home} />
</Switch>
</Suspense>
6.渐进式图像加载
你有没有见过 Medium.com 上的图片加载时很模糊?他们使用的是渐进式图片加载方式,也就是说,在加载高分辨率图片时,会先显示低质量版本的图片。
react-progressive-image包是将其集成到您的应用程序中的好方法。
...
import ProgressiveImage from "react-progressive-graceful-image";
import ProfileImgLarge from "../assets/img/profile-large.jpg";
import ProfileImgPlaceholder from "../assets/img/profile-placeholder.jpg";
...
<ProgressiveImage
src={ProfileImgLarge}
placeholder={ProfileImgPlaceholder}
>
{(src) => (
<ProfileImage src={src} alt="Profile of Sander de Bruijn" />
)}
</ProgressiveImage>
...
使用此技术,您可以使用例如<10kb 图像作为占位符直接向用户显示图像。
7. JS 动画代替 CSS 动画。
许多开发者实际上认为 CSS 动画比 JS 动画性能更好,但本文展示了在使用复杂动画时情况恰恰相反。此外,基于 JavaScript 的动画提供了更大的灵活性、更优的复杂动画工作流程以及丰富的交互性。
对于简单的动画,CSS 就足够了。但对于更复杂的动画,我建议使用 GSAP 库。
8. 生产构建
这才是最重要的。在开发过程中,React 提供了大量的插件来简化我们的开发工作。然而,用户并不需要这些插件。通过执行yarn build
(或 npm build),webpack 会在使用 create-react-app 时为我们构建输出文件夹。
渲染时间:<60ms
就是这样!希望你从本教程中有所收获。记得关注我,获取更多技巧和窍门。
文章来源:https://dev.to/sanderdebr/8x-react-performance-make-your-app-blazing-faster-1h1f