你可能没有关注过React性能优化
说起来,好久没有更新分享了,一直处于温水煮青蛙的状态。因为一些原因加上这个社会实在是太肉欲了,所以我被迫加入了内向者的大军,重温了很多知识,同时也发现了自己很多不足的地方,我也想借此机会让大家自愿,毕竟能力是自己的。原文链接先说几句今天想写的内容,大部分内容其实都可以从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(
);}}这段代码没什么好说的,每次点击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( );}}这段代码也是比较直观的说明了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问:如果我修改这行code