性能优化问题

真理也是一个幻觉,不过,是一个我们的生存,无法须弥或缺的幻觉。——《当尼采哭泣》

贼差的性能

1.性能问题的引出

对于上篇文章里面所出现的react应用来说,我给todos组件的reducer函数所接受的state参数设置默认值为一个具有10000个todo项的数组,由此渲染的时候,当我们想要对某一个todo进行反转操作以及删除操作的时候发现此时的性能极差,如上动图所示,当只反转一个todo的时候,很显然此时差不多有了五秒的计算时间,,,。


2.为什么会出现性能问题

虽然react已经提供了较好的渲染性能,但是还是存在可以优化性能的地方。每一次页面的更新都是对组件的重新渲染,但是并不是将所有之前渲染的组件都全部抛弃重来,有可能只是更新,差一点的话那就是卸载接着更新,最好的情况就是没有发生变化的组件不需要进行更新过程。首先我们需要知道的是,当页面由局部响应引起更新时,virtual dom能够在自己的理解范围内安全无误的计算出对dom树所需要做出的最少修改。

react组件的生命周期可以分为三个阶段:挂载,更新,卸载。

挂载过程的生命周期函数有:

  • componentWillMount
  • render
  • componentDidMount

更新过程,父组件向下传递props或组件自身执行setState方法时触发更新,生命周期函数有:

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

卸载过程的生命周期函数有:

  • componentWillUnmount

shouldComponentUpdate以及render函数中最为重要的方法。render方法决定了组件应该渲染出什么,而对于shouldComponentUpdate方法来说,它则决定了组件什么时候可以不必被渲染。对于Component类所提供的shouldComponentUpdate来说,它的默认实现是始终返回true的,因此在发生更新过程的时候,组件是会默认调用大多数生命周期函数,包括render函数,而render函数的结果就是virtual dom。正是由于这个原因,导致有的时候react网页的响应时的性能可能表现的不那么理想,如果想要优化性能的话,那么就尝试从shouldComponentUpdate方法下手。

我们需要注意的是,对于利用无状态函数所实现的组件来说,它的shouldComponentUpdate方法默认是继承Component类的该方法的,即保持更新。。而对于一个无状态函数实现的组件来说,它是修改不了shouldComponentUpdate方法的。。当然,对于这种组件的优化方法我们在后面会另作介绍。

当我们使用shouldComponentUpdate方法时,一般的做法是利用===比较符将nextProps和this.props以及nectState和this.state作比较。但是这种比较符是浅层的,对于两个拥有者两个相同值的不同对象来说,===是不等的。但是虽然此时两个对象是不等的,可当它作为同一个组件的props来说,这个组件在view层却并不用体现什么变化。幸亏对于那些基本数据类型变量来说,===比较符不会出现这种情况。

var obj1={"a":1};
var obj2={"a":2};
obj2 === obj1;//false

3.怎么优化性能

在上面我们也提到了想要优化组件性能的话,那就是对每个组件都实现其各自的shouldComponentUpdate方法。此时有两个问题:

  • 1.对于react-redux应用来说,我们一般将组件分为容器组件以及视图组件,对于容器组件来说,react-redux替我们封装了逻辑她完全由connect方法主导产生,那么我们又该如何定义容器组件的shouldComponentUpdate呢?
  • 2.可是对于无状态函数实现的组件来说,又得怎么为其定义呢?
  • 3.在上面也同时提到了,当我们实现shouldComponentUpdate的时候,常规做法就是利用===比较前后props以及前后state。可是当具有相同值的不同对象作为一个组件的props的话,那么这个组件单单利用===实现shouldComponentUpdate的话,那么这个组件将会进行重渲染。那么又该如何避免这种情况呢?
//利用===逻辑实现shouldComponentUpdate将无济于事
<Song  data={"url": "http://music.163.com/#/m/song?id=28068836&userid=67923532"} />

那么对于上面提到的三个问题又有什么解决办法呢?

1.对于由react-redux提供的connect方法来说,由它实现的容器组件会有一个与父类不同的shouldComponentUpdate实现,问题是对于容器组件的shouldComponentUpdate函数来说判断了哪些内容。我们知道对于容器组件来说,他自己可能接受props,容器组件不负责show,容器组件对应的子视图组件才负责被用户响应以及show。那么问题来了,这个容器组件的shouldComponentUpdate比较了那些内容?答案是即比较了自身的props也比较了自身的state——这些state会被mapStateToProps函数处理为作为视图组件的props。因此react-redux替容器组件实现的shouldComponentUpdate实现了自身在什么时候得重新渲染。需要如果我们的视图组件如果是无状态函数组件的话,那么connect之后还是无状态函数组件。那么问题又来了,我们的视图组件的shouldComponentUpdate又如何实现?如果父组件阻断了重渲染的话,那么它的子组件还会进行重渲染吗?已知这个子组件和virtual dom树的唯一联系就是通过其父组件,目前为止,我的猜测是既然他不会被无故牵连那么按理说不会平白无故被进行重渲染。????

2.对于无状态函数组件来说,如果我们希望给定义一个shouldComponentUpdate函数的话,那么按理来说是办不到的。毕竟这个组件都不是以class形式定义的,那么问题来了,该如何解决?答案是通过react-redux的connect,connect?对,我们可以利用connect方法为无状态函数组件包装一下,由于它不同于容器组件的子组件——视图组件,所以在connect的时候,不需要传入mapStateToProps以及mapDispatchToProps这两个参数。像下面这样的形式:

export default connect()(无状态函数组件)

需要注意的是,此时我们的无状态函数组件依旧是一个无状态组件,因此它是没有自己定义的shouldComponentUpdate?

3.在这种情况下,我们应该给组件传入一个只被创建一次的变量。当然,事实上,这种问题远比上面提到的那各个更加难以解决,但是要注意的就是别犯下面的错误:

<Funk style={{"color": "#233"}} />
<Rock song={() => {console.log("NARUTO")}} />

4.todoApp的具体优化

综上所诉,我们可以在我们的todoApp里寻找优化路径,比如像下面这样所列举出来的:

//todos/view/addTodo.js line 55
style = {{"width": "600px"}}

这样的话,对于react的shouldComponentUpdate来说,利用浅层比较符的话它识别不了style prop 并没有对造成渲染view产生什么不同的影响。因此会重复渲染,改进方式也很简单:

const inputStyle = {"width": "600px"};


style={inputStyle}

像上面这样的形式在我们的视图组件里面还存在很多,像下面这样的:

//todos/view/listItem.js 
<li style={{.....}} />
<button style={{.....}} />
<span style={{.....}} />

上面所列举出来的性能问题和上面的情况是一样的,所以优化方式也没有什么差别,问题是对于span的style props来说,它的某个style的属性是必须依赖组件的finished props的,因此,暂时想不出方法对他进行进一步优化。

还有就是对于一个问题就是对于无状态组件的优化问题,上面也提到了那就是利用react-redux提供的connect方法:

export default connect()(ListItem)

对于todoList文件,存在着一个较难优化的点:

//line 33, line 34
handleReverse={() => {handleReverse(todo.id)}}
handleDelete={() => {handleDelete(todo.id)}}

在这里我们给ListItem传入了handleReverse以及handleDelete这两个props,但是这两个props的值在每次创建ListItem组件的时候都会创建一次,所以我们需要给他传入一个只创建一次的函数,但是也得顺利完成这里的逻辑,那么可以像下面这样做:

<ListItem 
    key={todo.id}
    id={todo.id}
    value={todo.text} 
    handleReverse={handleReverse}
    handleDelete={handleDelete}
    finished={todo.finished}
/>

相应的在listItem文件增加下面这些逻辑

const mapStateToProps = (state, ownProps) => ({
    "value": ownProps.value,
    "finished": ownProps.finished
})

const mapDispatchToProps = (dispatch, ownProps) => ({
    "onReverse": () => {ownProps.handleReverse(ownProps.id)},
    "onDelete": () => {ownProps.handleDelete(ownProps.id)}
})

export default connect(mapStateToProps, mapDispatchToProps)(ListItem)

此时,最基本的性能问题已经差不多解决,下面可以看看效果:

改善了许多

项目地址

END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容