当前位置: 首页 > Web前端 > HTML

React组件性能优化详解

时间:2023-03-28 11:04:34 HTML

React组件性能优化最佳实践React组件性能优化的核心是降低渲染真实DOM节点的频率,降低VirtualDOM比较的频率。如果子组件的数据没有变化,则子组件不会被渲染。组件卸载前的清理操作下面的代码会在组件销毁时创建一个间隔定时器,并清除该定时器。1秒的间隔会触发渲染计数+1。如果组件销毁后不清除定时器,会一直消耗资源。importReact,{useState,useEffect}from"react"importReactDOMfrom"react-dom"constApp=()=>{let[index,setIndex]=useState(0)useEffect(()=>{lettimer=setInterval(())=>{setIndex(prev=>prev+1)console.log('定时器正在运行...')},1000)return()=>clearInterval(timer)},[])return(ReactDOM.unmountComponentAtNode(document.getElementById("root"))}>{index})}exportdefaultApp每次更新数据都会触发组件重新渲染。这里的优化是:组件销毁和清理的时机组件类的组件使用了一个纯组件PureComponent。什么是纯成分?中的引用地址是否相同,比较基本数据类型的值是否相同。为什么不直接进行diff操作,而是先进行浅比??较。浅比较没有性能消耗吗?与diff操作相比,浅比较会消耗更少的性能。diff操作会重新遍历整个virtualDOM树,而浅比较只对当前组件的state和props进行操作。importReactfrom"react"exportdefaultclassAppextendsReact.Component{constructor(){super()this.state={name:"张三"}}updateName(){setInterval(()=>this.setState({name:"张三"}),1000)}componentDidMount(){this.updateName()}render(){return(

)}}classRegularComponentextendsReact.Component{render(){console.log("RegularComponent")return
{this.props.name}
}}classPureChildComponentextendsReact.PureComponent{render(){console.log("PureChildComponent")return
{this.props.name}
}}组件挂载后,每1secondname,我们可以看到RegularComponent一直在渲染,即使数据没有改变。PureChildComponent只渲染一次,所以使用纯组件会比较props状态,相同的数据不会重新渲染。shouldComponentUpdate纯组件只能进行浅比较。要执行深度比较,请使用shouldComponentUpdate,它用于编写自定义比较逻辑。返回true重新渲染组件,返回false防止重新渲染。函数的第一个参数是nextProps,第二个参数是nextState。importReactfrom"react"exportdefaultclassAppextendsReact.Component{constructor(){super()this.state={name:"张三",age:20,job:"服务员"}}componentDidMount(){setTimeout(()=>this.setState({job:"chef"}),1000)}shouldComponentUpdate(nextProps,nextState){if(this.state.name!==nextState.name||this.state.age!==nextState.age){returntrue}returnfalse}render(){console.log("rendering")let{name,age}=this.statereturn
{name}{age}
}}即使继承自Component的组件定时器一直在修改数据,也不会触发纯函数组件的重新渲染。使用React.memo优化性能。memo的基本用途是将函数组件变成纯组件,对当前的props和上次的props做一个浅层的比较。如果相同会阻止组件重新渲染。importReact,{memo,useEffect,useState}from"react"functionShowName({name}){console.log("showNamerender...")返回
{name}
}constShowNameMemo=memo(ShowName)functionApp(){const[index,setIndex]=useState(0)const[name]=useState("张三")useEffect(()=>{setInterval(()=>{setIndex(prev=>prev+1)},1000)},[])return(
{index}
)}导出默认的Appmemo转账比较逻辑(使用memo方法自定义比较逻辑,进行深度比较。)importReact,{memo,useEffect,useState}from"react";functionShowName({person}){console.log("showNamerender...");return(
{person.name}丨{person.job}
);}functioncomparePerson(prevProps,nextProps){if(prevProps.person.name!==nextProps.person.name||prevProps.person.age!==nextProps.person.age){returnfalse}returntrue}constShowNameMemo=memo(ShowName,comparePerson);functionApp(){const[person,setPerson]=useState({name:"张三",工作:“开发人员”});useEffect(()=>{setInterval(()=>{setPerson((data)=>({...data,name:"浩轩"}));},1000);},[]);return(
);}exportdefaultApp;使用组件延迟加载使用组件延迟加载可以减少bundle文件的大小并加快组件渲染。路由组件懒加载详细答案参考前端进阶面试题lazy(()=>import(/*webpackChunkName:"Home"*/"./Home"))constList=lazy(()=>import(/*webpackChunkName:"List"*/"./List"))functionApp(){return(HomeListLoading
}>)}exportdefaultApp根据条件进行组件的懒加载(适用于不经常随条件切换的组件)importReact,{lazy,Suspense}from"react"functionApp(){letLazyComponent=nullif(true){lazyComponent=lazy(()=>import(/*webpackChunkName:"Home"*/"./Home"))}else{LazyComponent=lazy(()=>import(/*webpackChunkName:"List"*/"./List"))}return(Loading
}>)}exportdefaultApp使用Fragment来避免额外的标记为了满足这个条件,我们通常使用最后在外层添加一个div,但是这样会添加一个无意义的标记。如果每个组件都有这样一个无意义的标记,会增加浏览器渲染引擎的负担import{Fragment}from"react"functionApp(){return(
)}functionApp(){return(<>
messagea
messageb
)}不要使用内联函数定义使用后内联函数,render方法每次运行都会创建一个新的函数实例,导致React在进行VirtualDOM比较时,新旧函数比较不相等,导致React总是将新的函数实例绑定到元素上,而旧的函数实例必须交给垃圾收集器进行处理。错误演示:importReactfrom"react"exportdefaultclassAppextendsReact.Component{constructor(){super()this.state={inputValue:""}}render(){return(this.setState({inputValue:e.target.value})}/>)}}正确的做法是在组件中单独定义函数,并将函数绑定到事件上:import从“react”导出默认类AppextendsReact.Component{constructor(){super()this.state={inputValue:""}}setInputValue=e=>{this.setState({inputValue:e.target.value})}render(){return()}}在构造函数中this绑定在类组件中ifusingfn(){}定义这样的函数,函数的this默认指向undefined。也就是说,函数内部的this点需要修正。函数的this可以在构造函数中改正,也可以在行中改正。两者看起来差别不大,但是对性能的影响不同exportdefaultclassAppextendsReact.Component{constructor(){super()//方法一//构造函数只执行一次,所以修正后的this函数指向的代码只执行一次。this.handleClick=this.handleClick.bind(this)}handleClick(){console.log(this)}render(){//方法2//问题:render方法会调用bind方法生成新的函数实例每次执行。returnbutton}}类组件中的箭头函数在类组件中使用箭头函数不会有this指向的问题,因为箭头函数本身不会bindthisexportdefaultclassAppextendsReact.Component{handleClick=()=>console.log(this)render(){returnButton}}箭头函数有一个优势在这个指点问题上,它也有缺点。使用箭头函数时,添加的函数是类的实例对象属性,而不是原型对象属性。如果组件被多次复用,每个组件实例对象都会有相同的函数实例,降低了函数实例的复用性,造成资源浪费。上面说了,修正函数内部this点的最好方法还是在构造函数中使用bind方法进行绑定优化条件渲染频繁的挂载和卸载组件是一个很耗性能的操作。为了保证应用程序的性能,应该减少组件挂载和卸载的次数。在React中,我们经常会根据条件渲染不同的组件。条件渲染是必须做的优化操作。functionApp(){if(true){return(<>
)}else{return(<>
)}}上面代码中,当渲染条件发生变化时,React内部在做VirtualDOM对比时发现第一个组件是AdminHeader,现在第一个组件是Header,第二个组件是刚才的Header,现在第二个组件是Content。如果组件发生变化,React将卸载AdminHeader、Header和Content,并重新挂载Header和Content。这种挂载和卸载是不必要的。functionApp(){return(<>{true&&}
)}避免使用内联样式属性当使用内联样式给元素添加样式时,内联样式会被编译成JavaScript代码,通过JavaScript代码将样式规则映射到元素,浏览器将花费更多的时间执行脚本和渲染UI,从而增加组件的渲染时间。functionApp(){returnAppworks
}避免重复无限渲染当应用状态发生变化时,React会调用render方法,如果在render方法中继续改变应用程序的状态,递归调用render方法会导致应用程序报错。exportdefaultclassAppextendsReact.Component{constructor(){super()this.state={name:"张三"}}render(){this.setState({name:"Lisi"})return
{this.state.name}
}}与其他生命周期函数不同,render方法应该被视为纯函数。这意味着,在render方法中不要做以下事情,例如不调用setState方法,不使用其他方式查询和更改原生DOM元素,以及任何其他更改应用程序的操作。render方法的执行应该是基于状态的变化,这样可以保持组件的行为和render方法一致。避免数据结构突变。组件中props和state的数据结构要保持一致。数据结构突变会导致输出不一致。importReact,{Component}from"react"exportdefaultclassAppextendsComponent{constructor(){super()this.state={employee:{name:"张三",age:20}}}render(){const{姓名,年龄}=this.state.employeereturn(
{name}{age}this.setState({...this.state,employee:{...this.state.employee,age:30}})}>更改年龄
)}}