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

你可能没有关注过React性能优化

时间:2023-03-27 11:04:42 JavaScript

说起来,好久没有更新分享了,一直处于温水煮青蛙的状态。因为一些原因加上这个社会实在是太肉欲了,所以我被迫加入了内向者的大军,重温了很多知识,同时也发现了自己很多不足的地方,我也想借此机会让大家自愿,毕竟能力是自己的。原文链接先说几句今天想写的内容,大部分内容其实都可以从React官方文档中学习到。那我为什么还要写呢?因为作为一个写了快三年React的人,我并没有认真看过官方文档。我想说的可能是和我相似的人,同时加入一些自己的理解和看法。想请教几个关于性能优化的问题。我真的永远无法摆脱它。连面试官都要问几个问题,但说实话,我也不知道是React做的太好了,还是我做的项目太基础了,我基本上没遇到过什么性能问题,所以很长一段时间都不知道React有很多性能优化相关的API。我们先看一下代码。我直接在一个文件里定义多个组件给大家看。在正式编写代码时,一个文件就是一个组件。importReactfrom'react';classTestextendsReact.Component{componentDidUpdate(){console.log('TestcomponentDidUpdate');}render(){返回

;}}exportdefaultclassAppextendsReact.Component{constructor(props){super(props);this.state={count:0};this.handleClick=this.handleClick.bind(this);}handleClick(){this.setState((state)=>({count:state.count+1,}));}handleTestClick(){}render(){return(
{this.state.count}
click
);}}这段代码没什么好说的,每次点击click都会更新state,我现在问几个问题,你先想想看看~Test组件会不会每次点击都打印TestcomponentDidUpdate点击?如果我把Test组件的React.Component换成React.PureComponent,结果会和上面一样吗?如果不是,为什么?如果我将这行代码修改为{}}/>会怎样?的shouldComponentUpdate好像是从这个东西开始的。shouldComponentUpdate是React生命周期的一部分。大多数React开发人员至少听说过它。简单的说就是在这个函数中返回了一个布尔值,React会根据这个布尔值做出响应,判断组件是否需要重新渲染。shouldComponentUpdate接收两个参数,一个是更新后的props,另一个是更新后的状态。你可以通过比较这两个props和state来判断组件是否需要重新渲染。importReactfrom'react';classTestextendsReact.Component{componentDidUpdate(){console.log('TestcomponentDidUpdate');}//每次点击都会打印TestcomponentDidUpdate//当计数没有变化时添加这个函数不会打印TestcomponentDidUpdateshouldComponentUpdate(nextProps){if(this.props.count===nextProps.count){returnfalse;}返回真;}render(){返回
;}}exportdefaultclassAppextendsReact.Component{constructor(props){super(props);this.state={count:0};this.handleClick=this.handleClick.bind(this);}handleClick(){this.setState((state)=>({count:state.count,}));}render(){return(
{this.state.count}
click
);}}这段代码也是比较直观的说明了shouldComponentUpdate的用法,为什么要这样做呢?当只有一个Test组件时,可以能起到的作用不大,所以如果有一千甚至一万个Test,每次点击都会调用一千个或者一万个Test的componentDidUpdate,有点夸张。所以当你在使用循环渲染组件时一定要注意这一点,它可能会成为你应用程序的瓶颈。现在让我们解决第一个问题。每次点击时,Test组件会打印TestcomponentDidUpdate吗?是的,每次点击click时,Test组件都会打印TestcomponentDidUpdate,除非我们在Test中定义shouldComponentUpdate,返回false,防止重新渲染。相信大家对PureComponent的ReactAPI不会那么陌生。根据官方文档,Component和PureComponent非常相似。两者的区别是在PureComponent中实现了shouldComponentUpdate函数,这也是我说从shouldComponentUpdate入手的原因。importReactfrom'react';classTestextendsReact.PureComponent{componentDidUpdate(){console.log('TestcomponentDidUpdate');}//错误的用法shouldComponentUpdate(nextProps){if(this.props.count===nextProps.count){returnfalse;}返回真;}render(){返回
;如果你在PureComponent中使用了shouldComponentUpdate,你应该会得到这样一个警告,而且旁边还告诉我们PureComponent已经实现了shouldComponentUpdate函数。测试有一个名为shouldComponentUpdate()的方法。扩展React.PureComponent时不应使用shouldComponentUpdate。如果使用了shouldComponentUpdate,请扩展React.Component。官网文档说PureComponent是通过浅比较props和state来实现这个功能的,也就是浅比较,那么什么是浅比较呢?可以简单理解为a===b。里面还有一些争论,但不在本文讨论范围之内。举两个例子,大家可以自行搜索了解。让a=5;让b=5;让c={};让d={};console.log(a===b);//trueconsole.log(c===d);//falsein让我们来看一个由不当代码引起的问题。这部分大家一定要注意。importReactfrom'react';classTestextendsReact.PureComponent{//根据App传来的动物渲染组件//App中每次点击添加新动物后,这里还是原来的dogrender(){返回
测试:{this.props.animal.join(',')}
;}}exportdefaultclassAppextendsReact.Component{constructor(props){super(props);//默认是一只狗this.state={animal:['dog']};this.handleClick=this.handleClick.bind(this);}//每次点击都会给animal添加一个新的值//这里有一个bug,虽然animal.push方法更新了原来的数组//但是他们还是一个数组(这个说法有点奇怪),指针还是相同//读者可能需要去搜索了解JS中基本类型和引用类型的存储方式//所以当Test组件接收到新的动物时,通过浅对比会发现它们其实是一样的//这意味着测试不会重新渲染handleClick(val){const{animal}=this.state;animal.push(val)this.setState({animal,});}//根据状态中的动物渲染组件render(){return(
App:{this.state.animal.join(',')}
this.handleClick('cat')}>点击
);}}看到这里,相信你应该能回答第二个和第三个问题了,不过我们再来看看~Q:如果我把Test组件的React.Component换成React.PureComponent,结果和上面一样吗?如果不是,为什么?答:因为每次传递的props中的onClick是App组件中的handleTestClick,同时使用了PureComponent,所以每次浅比较都是一致的,所以不会打印TestcomponentDidUpdate问:如果我修改这行codefor{}}/>的结果呢?答:虽然用的是PureComponent,但是由于App每次调用render函数都会重新声明一个方法,这个方法和上次传给Test的方法不一样,所以每次点击Test还是会打印componentDidUpdate。其余内容补充。除了上面两个API,其他的API都差不多只是他们的改版,所以我就放在一起了。在我看来,memoReact.memo是PureComponent的无状态组件版本。如果您使用类,请使用PureComponent。如果您使用无状态组件,请使用备忘录。importReactfrom'react';exportdefaultReact.memo(functionTest(){return
TestComponent
;});//也可以接收第二个参数,类似shouldComponentUpdate//两个参数的lastprops,和currentprops//不传默认给两者做个浅对比,传了自己控制注意:该方法的返回值与shouldComponentUpdate相反,组件不会重新-当返回值为真时渲染。useCallback和useMemo这两个API是ReactHook的一部分,为什么要放在一起呢?因为它们非常相似,根据官方文档useCallback(fn,deps)等同于useMemo(()=>fn,deps)。因为有了ReactHook,我现在很少写类组件了。相信用过的人都知道原因。本文不对这方面进行详述。我只想问你另外一个问题:handleClick方法会不会每次都重新定义??从'react'导入React;导出默认函数Test(){consthandleClick=()=>console.log('click');returnTest
}答案是肯定的,不信可以验证一下。importReact,{useState}from'react';//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Setletset=newSet();导出默认函数App(){const[val,setVal]=useState(0);//比较两种方法以查看不同之处//consthandleClick=useCallback(()=>console.log('click'),[]);consthandleClick=()=>console.log('click');//集合中存储的唯一值,每次都会添加一个新值set.add(handleClick);控制台日志(设置大小);return(
{/*如果Test是一个特别复杂的组件,handleClick的每次更改都会导致它重新渲染*/}TestsetVal(val+1)}>click
);}用法和说明其实可以从上面的例子中看出,这里不再多做解释。有时我们不仅需要缓存一个函数,还需要一个值。在这种情况下,我们需要使用useMemo。两者的区别就在于此。看看用法就知道了。importReact,{useState,useMemo}from'react';letset=newSet();导出默认函数App(){const[val,setVal]=useState(0);//比较这三种方法以查看差异//constobj=useMemo(()=>({label:'Test',value:'test'}),[]);//constobj='obj';constobj={标签:'测试',值:'测试'};设置.添加(对象);控制台日志(设置大小);return(
{/*如果Test是一个特别复杂的组件,obj中的每一个变化都会导致它重新渲染*/}TestsetVal(val+1)}>click
);}这涉及到另一个JavaScript中的基本类型和引用类型的区别,类似于上面的浅层比较。你可以试试obj等于一个基本类型时的效果。Profiler最后说说Profiler,用来衡量其包裹的DOM树渲染带来的开销,帮助大家排查性能瓶颈,直接看使用情况。importReact,{Profiler,useState}from'react';functionTest(){return
Test
;}constcallback=(id,//发生提交阶段的Profiler树的“id”,//“mount”(如果组件树刚刚加载)或“update”(如果它被重新渲染)actualDuration,//此次更新花费的渲染时间committedbaseDuration,//估计渲染整个子节点而不使用memoization树需要的时间startTime,//React在本次更新中开始渲染的时间commitTime,//React在本次更新交互中提交的时间//属于本次更新中的交互集合)=>{console.log(id,phase,actualDuration,baseDuration,startTime,commitTime,interactions);};导出默认函数App(){const[val,setVal]=useState(0);返回(
测试setVal(val+1)}>点击
);}lastlast我所知道的React和性能优化相关的API都在上面。说实话,我们遇到的需要性能优化的场景太少了。这也是为什么面试官在选人的时候总是喜欢问这方面的问题,因为大部分人我都没有关注过,而我们要的也是那一小部分人。所以,多学点知识对自己真的是有好处的。只有了解更多的内容,才能更好地理解别人的代码。