React 组件性能优化最佳实践

React 组件性能优化最佳实践

React组件性能优化的核心是降低渲染真实DOM节点的频率,降低Virtual DOM比较的频率。如果子组件没有改变数据,则子组件不会被渲染。

组件卸载前清理

下面的代码会在组件挂载时创建一个间隔,并在组件销毁后清空定时器,每1秒触发一次渲染 计数+1 ,如果组件销毁后定时器没有清零,会继续消耗资源

 从“反应”导入反应,{ useState,useEffect }  
 从“react-dom”导入 ReactDOM  
  
 常量应用 = () => {  
 让 [index, setIndex] = useState(0)  
 使用效果(()=> {  
 让计时器 = setInterval( () => {  
 设置索引(上一个 => 上一个 + 1)  
 安慰。 log('计时器正在运行...')  
 }, 1000)  
 return () => clearInterval(timer)  
 }, [])  
 返回 (  
 < button onClick = {() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}> {index}</ button >  
 )  
 }  
  
 导出默认应用  
 复制代码

每次数据更新都会触发组件重新渲染,这里的优化是:组件销毁清理定时器

image.png

类组件使用纯组件 纯组件

什么是纯成分

纯组件将浅层比较组件输入数据。如果当前输入数据与上次输入数据相同,则不会重新渲染组件

什么是浅比较

比较内存中引用数据类型的引用地址是否相同,比较基本数据类型的值是否相同。

为什么不直接进行diff操作,而是先进行浅比​​较,浅比较没有性能消耗吗?

浅比较比进行差异比较消耗更少的性能。 diff 操作会重新遍历整个 virtualDOM 树,而浅层比较只对当前组件的 state 和 props 进行操作。

 从“反应”导入反应  
 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 这个。 state = { name: "张三"}  
 }  
 更新名称(){  
 setInterval( () => this.setState({ name: "张三"}), 1000)  
 }  
 组件DidMount() {  
 这个。更新名称()  
 }  
 使成为() {  
 返回 (  
 <div>  
 < 常规组件名称 = {this.state.name} />  
 < PureChildComponent name = {this.state.name} />  
 </ div >  
 )  
 }  
 }  
  
 类 RegularComponent 扩展 React.Component {  
 使成为() {  
 安慰。日志(“常规组件”)  
 返回 <div> {this.props.name}</ div >  
 }  
 }  
  
 类 PureChildComponent 扩展 React.PureComponent {  
 使成为() {  
 安慰。日志(“纯子组件”)  
 返回 <div> {this.props.name}</ div >  
 }  
 }  
 复制代码

组件挂载后,每1秒会设置一个定时器 姓名 ,我们可以看到 常规组件 一直渲染,即使数据没有改变。 纯子组件 只有一个渲染,所以使用纯组件将 道具`` 状态 为了比较,相同的数据不会被重新渲染。

image.png

应该组件更新

纯组件只能进行浅比较。要执行深度比较,请使用 shouldComponentUpdate,它用于编写自定义比较逻辑。

返回 true 以重新渲染组件, false 以防止重新渲染。

函数的第一个参数是nextProps,第二个参数是nextState。

 从“反应”导入反应  
  
 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 这个。 state = { 姓名:“张三”,年龄:20,工作:“服务员”}  
 }  
 组件DidMount() {  
 setTimeout( () => this.setState({ job: "chef" }), 1000)  
 }  
  
 shouldComponentUpdate(nextProps,nextState){  
 if ( this.state.name !== nextState.name || this.state.age !== nextState.age) {  
 返回真  
 }  
 返回假  
 }  
  
 使成为() {  
 安慰。日志(“渲染”)  
 让{姓名,年龄} =这个。状态  
 返回 < div > {姓名} {年龄}</ div >  
 }  
 }  
 复制代码

即使继承 零件 组件定时器不断修改数据,不会触发重新渲染

image.png

使用纯功能组件 反应备忘录 优化性能

备忘录的基本使用

把一个功能组件变成一个纯组件,对当前的 props 和最后一个 props 进行浅显的比较,如果相同则防止组件重新渲染。

 从“react”导入反应,{备忘录,useEffect,useState}  
  
 函数显示名称({名称}){  
 安慰。 log("showName 渲染...")  
 返回 < div > {名称}</ div >  
 }  
  
 const ShowNameMemo = memo(ShowName)  
  
 函数应用程序(){  
 const [索引,setIndex] = useState(0)  
 const [name] = useState("张三")  
 使用效果(()=> {  
 设置间隔(()=> {  
 设置索引(上一个 => 上一个 + 1)  
 }, 1000)  
 }, [])  
 返回 (  
 < div > {index} < ShowNameMemo name = {name} />  
 </ div >  
 )  
 }  
  
 导出默认应用  
 复制代码

memo 传递比较逻辑(使用 memo 方法自定义比较逻辑以执行深度比较。)

 从“react”导入反应,{备忘录,useEffect,useState};  
  
 函数显示名称({人}){  
 安慰。 log("showName 渲染...");  
 返回 (  
 < div > {person.name}丨{person.job} </ div >  
 );  
 }  
  
 功能比较人(prevProps,nextProps){  
 如果 (  
 prev道具。人。名称!== nextProps。人。姓名 ||  
 prev道具。人。年龄!== nextProps。人。年龄  
 ) {  
 返回假  
 }  
 返回真  
 }  
  
 const ShowNameMemo = memo(ShowName, comparePerson);  
  
 函数应用程序(){  
 const [person, setPerson] = useState({ name: "张三", job: "developer" });  
 使用效果(()=> {  
 设置间隔(()=> {  
 setPerson( ( data ) => ({ ...data,  name:  "haoxuan" }));  
 }, 1000);  
 }, []);  
 返回 (  
 <div>  
 < ShowNameMemo 人 = {人} />  
 </ div >  
 );  
 }  
  
 导出默认应用程序;  
  
 复制代码

组件的延迟加载

使用组件延迟加载可以减少包文件大小并加快组件渲染。

路由组件的延迟加载

 从“反应”导入反应,{懒惰,悬念}  
 从“react-router-dom”导入 { BrowserRouter, Link, Route, Switch }  
  
 const Home = lazy( () => import( /* webpackChunkName: "Home" */ "./Home"))  
 const List = lazy( () => import( /* webpackChunkName: "List" */ "./List"))  
  
 函数应用程序(){  
 返回 (  
 <浏览器路由器>  
 < 链接到 = "/" > 主页</ Link >  
 < 链接到 = "/list" > 列表</ Link >  
 < 开关 >  
 <Suspense fallback = { <div> 加载中</ div >}> < 路由路径 = "/" 组件 = {Home} 精确 />  
 < 路由路径 = "/list" 组件 = {List} />  
 </ Suspense >  
 </ Switch >  
 </ BrowserRouter >  
 )  
 }  
  
 导出默认应用  
 复制代码

根据条件延迟加载组件(适用于不频繁随条件切换的组件)

 从“反应”导入反应,{懒惰,悬念}  
  
 函数应用程序(){  
 让 LazyComponent = null  
 如果真实) {  
 LazyComponent = lazy( () => import( /* webpackChunkName: "Home" */ "./Home"))  
 } 别的 {  
 LazyComponent = lazy( () => import( /* webpackChunkName: "List" */ "./List"))  
 }  
 返回 (  
 <Suspense fallback = { <div> 加载中</ div >}> < 懒惰组件 />  
 </ Suspense >  
 )  
 }  
  
 导出默认应用  
 复制代码

使用 Fragment 避免额外的标记

为了满足这个条件,我们通常会在最外层添加一个div,但这会添加一个无意义的标记。如果每个组件都有这样一个无意义的标记,浏览器渲染引擎的负担将会加重。

 从“反应”导入{片段}  
  
 函数应用程序(){  
 返回 (  
 <片段>  
 <div> 消息一个</ div >  
 <div> 消息 b</ div >  
 </ Fragment >  
 )  
 }  
 复制代码 函数应用程序(){  
 返回 (  
 <>  
 <div> 消息一个</ div >  
 <div> 消息 b</ div >  
 </>  
 )  
 }  
 复制代码

不要使用内联函数定义

使用内联函数后,render方法每次运行都会创建一个新的函数实例,导致React在比较Virtual DOM时对新旧函数的比较不同,导致React总是绑定一个新的函数实例到元素上,旧的函数实例交给垃圾收集器处理。

错误示例:

 从“反应”导入反应  
  
 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 这个。状态 = {  
 输入值:“”  
 }  
 }  
 使成为() {  
 返回 (  
 <输入  
 值 = {this.state.inputValue}  
 onChange = {e => this.setState({ inputValue: e.target.value })} />  
 )  
 }  
 }  
 复制代码

正确的做法是在组件中单独定义函数,并将函数绑定到事件:

 从“反应”导入反应  
  
 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 这个。状态 = {  
 输入值:“”  
 }  
 }  
 设置输入值 = e => {  
 这个。 setState({ inputValue: e.target.value })  
 }  
 使成为() {  
 返回 (  
 < 输入值 = {this.state.inputValue} onChange = {this.setInputValue} />  
 )  
 }  
 }  
 复制代码

在构造函数中执行此绑定

如果在类组件中使用 fn() {} 定义函数,函数 this 默认指向 undefined。也就是说,函数内部的this点需要修正。

函数的this可以在构造函数中修正,也可以内联修正,两者看起来差别不大,但是对性能的影响是不一样的

 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 //方法一  
 // 构造函数只执行一次,所以this指向的正确代码只执行一次。  
 这个。手柄点击 = 这个。手柄点击。绑定(这个)  
 }  
 手柄点击(){  
 安慰。日志(这个)  
 }  
 使成为() {  
 //方法二  
 // 问题:每次执行render方法,都会调用bind方法生成一个新的函数实例。  
 return < button onClick = {this.handleClick.bind(this)} > 按钮</ button >  
 }  
 }  
 复制代码

类组件中的箭头函数

在类组件中使用箭头函数不存在 this 指向问题,因为箭头函数本身并没有绑定 this。

 导出默认类 App 扩展 React.Component {  
 handleClick = () => 控制台。日志(这个)  
 使成为() {  
 return < button onClick = {this.handleClick} > 按钮</ button >  
 }  
 }  
 复制代码

箭头函数在 this-pointing 问题中具有优势,但它们也有缺点。

使用箭头函数时,该函数被添加为类的实例对象属性,而不是原型对象属性。如果组件被多次复用,那么每个组件实例对象中都会有一个相同的函数实例,降低了函数实例资源的复用性,就是对资源的浪费。

综上所述,修正函数内部this指针的最佳实践还是使用构造函数中的bind方法进行绑定

优化条件渲染

频繁安装和卸载组件是一项性能密集型操作。为了保证应用的性能,应减少元件的装卸次数。

在 React 中,我们经常有条件地渲染不同的组件。条件渲染是必须做的优化。

 函数应用程序(){  
 如果真实) {  
 返回 (  
 <>  
 < 管理头 />  
 < 标题 />  
 <内容/>  
 </>  
 )  
 } 别的 {  
 返回 (  
 <>  
 < 标题 />  
 <内容/>  
 </>  
 )  
 }  
 }  
 复制代码

上面代码中,当渲染条件发生变化时,React内部对比Virtual DOM,发现第一个组件是AdminHeader,现在第一个组件是Header,第二个组件是Header,现在第一个组件是Header。第二个组件是内容。当组件发生变化时,React 会卸载 AdminHeader、Header 和 Content,并重新挂载 Header 和 Content。这种安装和卸载是不必要的。

 函数应用程序(){  
 返回 (  
 <> {true && < AdminHeader /> } < 标头 />  
 <内容/>  
 </>  
 )  
 }  
 复制代码

避免使用内联样式属性

使用内联样式给元素添加样式时,内联样式会被编译成 JavaScript 代码,样式规则会通过 JavaScript 代码映射到元素上,浏览器会花费更多时间执行脚本和渲染 UI,从而增加组件的渲染时间。

 函数应用程序(){  
 return < div style = {{ backgroundColor: "skyblue " }}> 应用正常</ div >  
 }  
 复制代码

避免重复无限渲染

当应用程序状态发生变化时,React 会调用 render 方法。如果在render方法中继续改变应用状态,会递归调用render方法,应用会报错。

 导出默认类 App 扩展 React.Component {  
 构造函数(){  
 极好的()  
 这个。 state = { name: "张三"}  
 }  
 使成为() {  
 这个。 setState({ name: "李四"})  
 返回 <div> {this.state.name}</ div >  
 }  
 }  
 复制代码

与其他生命周期函数不同,render 方法应被视为纯函数。这意味着,在 render 方法中,不要做调用 setState 方法之类的事情,不要使用其他方式查询和更改原生 DOM 元素,以及对应用程序的 Any 操作进行其他更改。 render 方法的执行是基于状态变化的,它使组件的行为和渲染保持一致。

避免数据结构突变

组件中 props 和 state 的数据结构要一致,数据结构的突变会导致输出不一致。

 从“反应”导入反应,{组件}  
  
 导出默认类 App 扩展组件 {  
 构造函数(){  
 极好的()  
 这个。状态 = {  
 员工: {  
 name:  "Zhang San",  
 年龄:20  
 }  
 }  
 }  
 使成为() {  
 常量 { 姓名,年龄 } = 这个。状态。员工  
 返回 (  
 < div > {name} {age} < 按钮  
 onClick = {() => this.setState({ ...this.state, employee: { ...this.state.employee, age: 30 } }) } > 更改年龄</ button >  
 </ div >  
 )  
 }  
 }  
 复制代码

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。

这篇文章的链接: https://homecpp.art/0913/8285/2313

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/36202/44051409

posted @ 2022-09-14 09:45  哈哈哈来了啊啊啊  阅读(108)  评论(0编辑  收藏  举报