- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
我们是 袋鼠云数栈 UED 团队 ,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值.
本文作者:空山 。
由于笔者最近在开发中遇到了一个重复渲染导致子组件状态值丢失的问题,因此关于性能优化做了以下的分析,欢迎大家的交流 。
我们在日常的项目开发中往往会把页面拆分成一个个的组件,通过拼装的方式来实现整体的页面效果,所以与其说去优化 React,不如聚焦在现有的组件中,思考🤔如何去设计一个组件才能提高他的性能,从而提高整个项目的性能以及交互的流畅性.
在我们初学 React 的时候相信大家或多或少运行过这样的 demo:
import React, { useState } from 'react';
import { Button, Space } from 'antd';
import 'antd/dist/antd.css';
import './index.css';
const Parent: React.FC = () => {
const [count, setCount] = useState(0);
return (
<Space direction="vertical">
{count}
<Button type="primary" onClick={() => setCount(count + 1)}>
count + 1
</Button>
<Children />
</Space>
);
};
const Children: React.FC = () => {
console.log('更新了子组件');
return <div>这是子组件</div>;
};
export default Parent;
代码很少就是个简单的父组件嵌套子组件的情况,我们测试运行也没啥问题,大多数时候我们可能也就这么开发了。 但是我们观察下,每次当我们点击按钮进行 count + 1 时会更新子组件,这一点从控制台打印的信息可以看出。虽然 React 中有 diff 算法决定是否需要切实更新 DOM 元素,但是其内部的定义的一些函数还是会执行,而且 diff 会遍历整棵 virtualDOM 树也会有一定的性能消耗,那能不能优化下这个勒.
由于 React 中的组件分为 Function 组件和 Class 组件,优化的手段根据其特性不同也分为两类 。
React.memo 是 React 提供的一个高阶组件,用于优化组件的性能。它可以在某些情况下避免不必要的组件重新渲染,从而提高应用程序的性能。其使用方式分为两种:
import React from 'react';
const areEqual = (prevProps, nextProps) => {
// 自定义比较逻辑
// 返回 true 表示两个 props 相等,不需要重新渲染
// 返回 false 表示两个 props 不相等,需要重新渲染
return prevProps.value === nextProps.value;
};
const MyComponent = React.memo((props) => {
console.log('Rendering MyComponent');
return <div>{props.value}</div>;
}, areEqual);
useCallback 是 React 中的一个 Hook,用于优化性能和避免不必要的渲染。它主要用于创建一个稳定的回调函数,并在依赖项未发生变化时缓存该函数。 示例代码:
import React, { useState, useCallback, useEffect } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
useEffect(() => {
handleClick();
}, [handleClick]);
// 使用 useCallback 缓存回调函数 handleClick
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
注意⚠️:并不是必需的,它主要用于解决特定的性能问题。在大多数情况下,使用普通的函数定义也是有效的。只有在性能优化成为问题时,才需要考虑使用 useCallback 。过度使用该 useCallback 会导致内存占用增加(每个缓存的回调函数都会占用内存),代码复杂度增加可读性变差并且难以维护。 可以遵循以下原则:
useEffect
、 useMemo
)时,才使用 useCallback
useCallback
的依赖项数组,以确保缓存的回调函数在依赖项未发生变化时不会重新创建 它用于在组件渲染过程中进行记忆化计算,以避免不必要的重复计算,提高应用的性能。 使用场景:
useMemo
将计算结果缓存起来 useMemo
缓存该组件的输出,避免不必要的重新渲染
import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
// 使用 useMemo 缓存结果
const processedData = useMemo(() => {
// 执行昂贵的计算或处理逻辑
// 这里只是一个简单的示例,实际场景可能更复杂
console.log('Processing data...');
return data.map(item => item * 2);
}, [data]); // 依赖项: 当 data 发生变化时重新计算
return (
<div>
{/* 渲染使用 useMemo 缓存的结果 */}
<ul>
{processedData.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
!!仅支持React 15.3及以上版本 PureComponent 是继承自 React.Component 的一个子类,它额外实现了shouldComponentUpdate 方法,并通过对组件的 props 和 state 进行浅层比较来确定是否需要重新渲染组件。 使用方式:
class App extends React.PureComponent
如果 props 和 state 没有发生改变,就不会进入 render 节点,省去了生成 Virtual DOM 和 Diff 的过程。 低版本可以使用 PureRenderMixin ,使用浅比较来决定是否应该触发组件的重新渲染。它会自动为组件添加一个 shouldComponentUpdate 方法,该方法会比较新的 props 和 state 与当前的 props 和 state ,并根据比较结果决定是否重新渲染组件 。
import PureRenderMixin from 'react-addons-pure-render-mixin';
class App extends React.Component {
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
// 组件的其他方法和生命周期函数
// ...
}
如果想对渲染进行更加细微的控制,或者是对引用类型进行渲染控制我们可以使用 shouldComponentUpdate ,通过返回 true 进行更新, false 阻止不必要的更新.
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 对比新旧属性和状态
if (this.props.value === nextProps.value && this.state.count === nextState.count) {
return false; // 属性和状态相同,不需要重新渲染
}
return true; // 需要重新渲染
}
render() {
return <div>{this.props.value}</div>;
}
}
特别注意⚠️:合理使用,手动实现 shouldComponentUpdate 可能会增加代码的复杂性,并且过度使用它可能会导致更多的维护问题。只有在确实需要优化性能时,才建议使用它.
当我们在类组件中绑定类方法通过需要绑定他的 this 指向 。
// 方式一
render() {
return <Button onClick={this.handleClick.bind(this)}>测试按钮</Button>
}
// 方式二
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
虽然用起来是一样的,但是第一种方式在 render 的时候,每次会 bind this 生成新的函数实例,而第二种只会执行一次.
组件中注册的全局的监听器、定时器等,需要在组件卸载的时候进行清理,防止后续的执行影响性能以及内存泄露等问题 。
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
// 定义定时器
const timer = setInterval(() => {
setCount(count => count + 1);
}, 1000);
const handleOnResize = () => {
console.log('Window resized');
}
// 定义监听器
const listener = window.addEventListener('resize', handleOnResize);
// 在组件卸载时清除定时器和监听器
return () => {
clearInterval(timer);
window.removeEventListener('resize', handleOnResize);
};
}, []);
return (
<div>
<p>{count}</p>
</div>
);
}
export default Timer;
React版本支持16.6及以上版本 低版本可以考虑使用第三方库(如react-loadable)来实现类似的懒加载效果 。
通过组件懒加载可以将代码分割成更小的块,并且只有在需要时才会被加载 当用户访问某个特定页面时,只有与该页面相关的代码会被下载和执行,而其他代码则不会被加载。这样可以使应用程序更快地启动,并减少页面响应延迟.
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
}
export default App;
在React中我们通常使用 lazy 和 Suspense 互相配合的方式进行懒加载,使用 lazy 方法来懒加载 Home 和 About 组件,使用 Suspense 可以在组件加载完成之前显示一个自定义的加载指示器或占位符,从而提高用户体验.
!!仅支持React 16.2及以上版本 React Fragment 允许你在 React 组件中返回多个元素而不需要添加额外的根节点,比如在某些情况下你的组件返回的是一个 list 的元素集合而他的根元素在父组件中,你想要的结构是根节点内直接元素的集合而不想再包裹一层,可以使用这种方式解决 。
javascript
import React, { lazy, Suspense } from 'react';
function TdList() {
return (
<React.Fragment>
<td>Hello, World!</td>
<td>This is a paragraph.</td>
</React.Fragment>
);
}
function Table() {
return (
<table>
<tr>
<TdList />
</tr>
</table>
);
}
除此以外还具有一下优势:
有时候我们也会使用 <></> ,被称为空标签或者隐式 Fragment ,其用法和 Fragment 相同,唯一的区别在于空标签不能添加任何属性,而后者可以,比如说在某些场景下需要给父元素增加 key 值。此时只能使用 Fragment .
当我们在 React 中使用内联函数时,每次重新 render 将导致生成新的函数实例从而为元素绑定新的函数,在非嵌套的组件使用时影响不大,但是如果存在 嵌套组件并且该内联函数是作为 props 传递给子组件 时将会导致子组件重新渲染,即使内联函数里的代码相同的情况下.
useCallback
进行缓存
// bad
import React, { useCallback } from 'react';
function MyComponent() {
return (
<button
onClick={() => {
// 处理点击事件
}}
>
Click me
</button>
);
}
// good
import React, { useCallback } from 'react';
function MyComponent() {
const handleClick = useCallback(() => {
// 处理点击事件
}, []);
return (
<button onClick={handleClick}>Click me</button>
);
}
假设你有一个需要渲染大量数据的列表组件,每个列表项都是一个独立的子组件。当你对这个列表进行添加、删除或重新排序操作时,React 需要计算出哪些子组件需要更新.
import React from 'react';
function MyComponent(props) {
const data = props.data; // 假设这是一个包含大量数据的数组
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
未使用 key 的情况下,React 将无法区分列表项之间的差异,而会重新渲染整个列表。使用 key 可以帮助 React 提高性能,它可以通过比较新旧的 key 来确定是否需要更新特定的列表项。这样,当你对列表进行操作时,React 只会针对变化的部分进行更新。 注意⚠️:尽量不要使用 index 作为 key 值 。
在我们浏览很多图像、卡片、列表时我们往往不会立马加载所有的资源,主要是因为一个是很浪费资源,另一个就是在不可见区域内也必要进行加载。借鉴这个思想,我们是否可以在组件在视口范围内进行加载呢,其余不进行加载—— IntersectionObserver 或者是一些现有的库如 react-loadable-visibility (通过 react-loadable 按需加载组件 + Intersection Observer API 监听组件的可见性) 。
import React from "react";
import { Button } from "antd";
import LoadableVisibility from "react-loadable-visibility/react-loadable";
const LoadingComponent = () => <div>Loading...</div>;
const MyComponent = LoadableVisibility({
loader: () => import("./MyComponent"),
loading: LoadingComponent
});
const App = () => {
return (
<div>
<h1>My App</h1>
<MyComponent />
</div>
);
};
export default App;
当 MyComponent 组件进入视口时,它们才会被加载和渲染,而在加载过程中,会显示 LoadingComponent 组件作为占位符,需要注意的是,确保在支持 Intersection Observer API 的浏览器中进行 。
页面中包含并非立即需要的组件或资源的代码或数据,立即加载这些资源将阻塞主线程,而这些功能当用户不去触发某些操作是也是用不到的,就可以采用这种方式。 比如我们有个需求是点击“滚动到顶部”按钮时以动画方式滚动回页面顶部,这里我们用到了 react-scroll 这个包,那可以在与按钮交互时加载它 。
handleScrollToTop() {
import('react-scroll').then(scroll => {
scroll.animateScroll.scrollToTop({
})
})
}
通过 Web Worker 创建多线程的环境,主线程把一些任务分配给后者运行,不会阻塞主线程的运行,使交互更加流畅。 适用场景:
仅渲染可见区域的 dom 元素而不必要渲染全部,提高渲染性能。可以使用 React-virtualized 或者是 React-window 等包.
以上就是笔者对性能优化方面的研究🧐和总结,如果大家在日常开发中有这样的诉求可以参考以上几种方式.
欢迎关注【袋鼠云数栈UED团队】~ 袋鼠云数栈UED团队持续为广大开发者分享技术成果,相继参与开源了欢迎star 。
最后此篇关于React组件设计之性能优化篇的文章就讲到这里了,如果你想了解更多关于React组件设计之性能优化篇的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
问题是,当用户回复彼此的帖子时,我必须这样做: margin-left:40px; 对于 1 级深度 react margin-left:80px; 对于 2 层深等 但是我想让 react div
我试图弄清楚如何将 React Router 与 React VR 连接起来。 首先,我应该使用 react-router dom/native ?目前尚不清楚,因为 React VR 构建在 Rea
我是 React 或一般编码背景的新手。我不确定这些陈述之间有什么区别 import * as react from 'react' 和 import react from 'react' 提前致谢!
我正在使用最新的稳定版本的 react、react-native、react-test-renderer、react-dom。 然而,react-native 依赖于 react@16.0.0-alp
是否 react 原生 应用程序开发可以通过软件架构实现,例如 MVC、MVP、MVVM ? 谢谢你。 最佳答案 是的。 React Native 只是你提到的那些软件设计模式中的“V”。如果你考虑其
您好我正在尝试在我的导航器右按钮中绑定(bind)一个功能, 但它给出了错误。 这是我的代码: import React, { Component } from 'react'; import Ico
我使用react native创建了一个应用程序,我正在尝试生成apk。在http://facebook.github.io/react-native/docs/signed-apk-android.
1 [我尝试将分页的 z-index 更改为 0,但没有成功] 这是我的codesandbox的链接:请检查最后一个选择下拉列表,它位于分页后面。 https://codesandbox.io/s/j
我注意到 React 可以这样导入: import * as React from 'react'; ...或者像这样: import React from 'react'; 第一个导入 react
我是 react-native 的新手。我正在使用 React Native Paper 为所有屏幕提供主题。我也在使用 react 导航堆栈导航器和抽屉导航器。首先,对于导航,论文主题在导航组件中不
我有一个使用 Ignite CLI 创建的 React Native 应用程序.我正在尝试将 TabNavigator 与 React Navigation 结合使用,但我似乎无法弄清楚如何将数据从一
我正在尝试在我的 React 应用程序中进行快照测试。我已经在使用 react-testing-library 进行一般的单元测试。然而,对于快照测试,我在网上看到了不同的方法,要么使用 react-
我正在使用 react-native 构建跨平台 native 应用程序,并使用 react-navigation 在屏幕之间导航和使用 redux 管理导航状态。当我嵌套导航器时会出现问题。 例如,
由于分页和 React Native Navigation,我面临着一种复杂的问题。 单击具有类别列表的抽屉,它们都将转到屏幕 问题陈述: 当我随机点击类别时,一切正常。但是,在分页过程中遇到问题。假
这是我的抽屉导航: const DashboardStack = StackNavigator({ Dashboard: { screen: Dashboard
尝试构建 react-native android 应用程序但出现以下错误 info Running jetifier to migrate libraries to AndroidX. You ca
我目前正在一个应用程序中实现 React Router v.4,我也在其中使用 Webpack 进行捆绑。在我的 webpack 配置中,我将 React、ReactDOM 和 React-route
我正在使用 React.children 渲染一些带有 react router 的子路由(对于某个主路由下的所有子路由。 这对我来说一直很好,但是我之前正在解构传递给 children 的 Prop
当我运行 React 应用程序时,它显示 export 'React'(导入为 'React')在 'react' 中找不到。所有页面错误 see image here . 最佳答案 根据图像中的错误
当我使用这个例子在我的应用程序上实现 Image-slider 时,我遇到了这个错误。 import React,{Component} from 'react' import {View,T
我是一名优秀的程序员,十分优秀!