当前位置: 首页 > 科技观察

提升React性能的七个小技巧

时间:2023-03-18 14:19:00 科技观察

介绍了一些刚开始学习React,或者从其他框架转入React的开发者,一开始可能不太关注性能。因为需要一定的时间才能发现新学的框架的性能不足。后来,由于经验不足,这些开发人员在编写代码时犯了一些小错误,最终累积起来导致性能不佳。此外,他们将很难解决问题。在这里,我们将探索7个技巧,帮助您在构建任何类型的应用程序时避免大多数React性能问题。1.解决重复渲染问题我们大多数人都知道虚拟DOM是如何工作的,但最重要的是检测何时触发树比较。当我们可以跟踪它时,我们可以控制组件的重新渲染并最终防止意外的性能流。令人惊讶的是,它并不难捕捉。首先,将ReactDevtool扩展添加到浏览器。然后打开浏览器开发者工具(Chrome中的Option+?+J(在macOS上),或Shift+CTRL+J(在Windows/Linux上)。选择组件单击设置图标并选中“HighlightwhencomponentrendersUpdate”,这就是现在,当我们与UI交互时,它会在当前重新渲染的元素上显示一个绿色边框。了解这一点后,我们可以分析我们的任何React组件并重构它们以避免不必要的重新渲染。2.减少重新渲染通过拆分组件进行渲染如果我们能够减少意外重新渲染元素的数量,它将解决React中的大多数性能问题。但是我们必须首先回答这个问题:“什么触发了重新渲染?”。答案很简单,状态变化。每次组件状态变化时,它都会唤醒树比较,也称为协调,并重新呈现状态上下文的元素。状态上下文,是初始化这种状态的组件。意思是,如果我们有一个有很多状态的巨大组件(不需要o相互依赖)其中一个状态发生变化,它会重新渲染整个组件元素,这肯定不是我们想要的。那么,解决方案是什么?解决方案是通过将组件的一部分和它的一些状态移动到它自己的子组件中来分离状态上下文,现在,让我们看一下这个例子:假设我们有一个带有搜索过滤器的表组件。搜索过滤器过滤器是一种受控输入,其状态在输入文本更改后更新。它看起来是这样的:当我们开始在搜索输入字段中输入时会发生什么?是的,它重新呈现整个表单元素。发生这种情况是因为输入状态上下文与表组件共享相同的上下文。现在,让我们通过将输入元素及其状态移动到一个单独的组件并将其注入表组件来尝试我们的解决方案。奇迹发生了,表格组件不再重新渲染。我们稍后可以通过从输入中发出事件来增强功能,以准确控制我们希望输入何时影响表单元素。好的做法是将组件拆分为单独的状态上下文,以避免冗余的重新渲染。3.什么是实例重创建,如何避免?我们已经看到状态更改会触发组件重新渲染,但我们需要考虑另一个重要的副作用。当状态发生变化和协调时,它会重新初始化整个组件实例并保留新的状态值。这对我们来说意味着在协调期间将重新创建所有函数实例以能够考虑新的状态值,我们不需要它,在大多数情况下一个函数可以只依赖于几个状态而我们不需要'想要重新创建不依赖于已更改状态的创建函数实例。这是一个提高性能的机会,我们有几个解决方案:useCallback和useRef。我们来看一个例子:const{someState,setSomeState}=useState('')const{otherState,setOtherState}=useState('')constfoo=()=>{console.log(someState)}这是最常见的例子。我们有依赖于状态someState的foo。当someState发生变化时,它将重新创建一个新的foo实例。这段代码的问题在于,即使其他一些状态发生变化,比如otherState,foo也会被重新创建,而这实际上并不是我们想要的。我们可以使用useCallback来告诉React我们函数的状态依赖关系在重新创建实例时要更明确:const{someState,setSomeState}=useState('')const{otherState,setOtherState}=useState('')constfoo=useCallback(()=>{console.log(someState)},[someState])在此示例中,我们将一组依赖项传递给useCallback挂钩。更好的是,foo将避免其他状态更改。另一种选择是使用useRef。useRef-你可以把它想象成useState,但不会触发组件重新渲染(UI不会更新)。useRef没有依赖列表,所以我们需要将someState作为foo属性传递:const{someState,setSomeState}=useState('')const{otherState,setOtherState}=useState('')constfoo=useRef((currentSomeState)=>{console.log(currentSomeState)}).current;在这种情况下,我们根本不会重新创建foo实例。结论:使用useCallback和useRef来控制函数实例的重新创建。4、不要偷懒去加载React默认的同步渲染组件。这意味着该组件将等到其子项被渲染后再渲染自己。无需等待,尤其是在某些子组件未耦合的情况下。它可能会导致页面挂起。假设我们单击某个导航链接,将我们重定向到另一个页面。导航将等待所有页面组件呈现以完成重定向。它会影响用户体验,人们不会等待而离开您的网站。我们需要让页面内容异步呈现,以免影响导航。解决方案是将页面组件包装在React.lazy(()中并告诉React完成导航,然后等待页面组件完成渲染:constPageComponent=React.lazy(()=>import('./PageComponent'));稍后我们可以使用在页面组件尚未准备好时显示一些加载动画。Loading...

}>这并不意味着我们必须在任何地方都使用Lazyload组件,当我们在不会对性能造成太大影响的地方使用它时,它可能会导致过度工程。另一种情况是某些组件可能会隐藏在UI中默认所以我们不必等待它们。例如模态窗口、对话框、抽屉和可折叠侧面板。延迟加载页面组件和隐藏的UI组件。5.什么时候使用Reactfragments?这种情况经常发生,当我们在JSX中构建一些布局并想要对我们的元素进行分组时,在大多数情况下我们使用
标签。或者,例如,我们有父子HTML标记,我们希望将其移动到单独的组件中:
  • Item1
  • <---|想把它移给孩子
  • |
  • 项目2
  • <---||
因此,当我们将
  • 移动到一个单独的组件中时,例如:constLi=()=>{return(
  • Item1
  • Item2
  • )}并更改它:
    渲染后,它将如下所示:
    • Item1
    • Item2
    这将创建一个我们不需要的额外
    节点。这将使我们的DOM树更加嵌套,从而减慢协调过程。相反,我们可以将我们的
    子元素包装到片段中。最初,Fragments允许您对DOM元素进行分组,插入后只会导致一次回流。在React中,Fragments还允许您减少不必要的节点。当你想对元素进行分组时,你唯一需要做的就是使用Fragment而不是
    :constLi=()=>{return(<>/*or,or*/
  • Item1
  • Item2
  • )}就是这样,就这么简单。如果要对元素进行分组以减少节点数,请使用Fragment。6.避免在列出的元素中使用索引作为键。大家都知道,如果没有,Eslint会在列出的元素中强制使用key,例如:
      Item1Item2
    React中的关键是唯一标识符,它帮助React指向列表中的正确元素并更新正确的元素。如果我们使用索引作为列表中的键,比如:
      {[1,2].map((val,index)=>Item{val})}
    我们将元素映射到它的索引。但是如果我们进行排序,列表中元素的顺序可能会改变,初始键将不再指向正确的元素。始终使用唯一id作为列出元素的键,如果对象没有它,您可以使用外部库(如uid)显式分配它。7.避免传播道具这是今天最后的修改和调整技巧。已经有很多了,你一定见过,甚至还自己制作了传播道具。比如:constInput=({onChange,...props})=>(onChange(e.target.value)}/>);它不仅迫使您猜测实际输入接收的属性是什么,而且您还在输入元素中创建了一堆您不一定需要的属性。让它显式,并且不要害怕根据需要传递尽可能多的属性,你总是可以将它们分组到某个对象中:constInput=({onChange,inputProps:{value,type,className}})=>(<输入className={className}type={type}value={value}onChange={e=>onChange(e.target.value)}/>);太好了,现在更具可读性。永远不要传播道具,分别传递每个属性。总之,我认为您可能已经知道Eslint强制执行的一些调整,但现在您知道为什么遵循它们很重要,此外,您可以分析您的代码,这将为您提供改进空间。