转载自https://www.cnblogs.com/penghuwan/p/6707254.html

凡是参阅过react官方英文文档的童鞋大体上都能知道对于一个组件来说,其state的改变(调用this.setState()方法)以及从父组件接受的props发生变化时,会导致组件重渲染,正所谓"学而不思则罔",在不断的学习中,我开始思考这一些问题:

1.setState()函数在任何情况下都会导致组件重渲染吗?如果setState()中参数还是原来没有发生任何变化的state呢?
2.如果组件的state没有变化,并且从父组件接受的props也没有变化,那它就一定不会重渲染吗?
3.如果1,2两种情况下都会导致重渲染,我们该如何避免这种冗余的操作,从而优化性能?
下面我就用实例一一探讨这些问题:
没有导致state的值发生变化的setState是否会导致重渲染 ——【会!】
按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

demo:
点击1一共15次,其间demo没有发生任何变化
控制台输出:(我点击了1一共15次  _(:3 」∠)_)
那么问题就来了,我的UI明明就没有任何变化啊,为什么要做着中多余的重渲染的工作呢?把这工作给去掉吧!
于是这里react生命周期中的shouldComponentUpdate函数就派上用场了! shouldComponentUpdate函数是重渲染时render()函数调用前被调用的函数,它 接受两个参数:nextProps和nextState,分别表示下一个props和下一个state的值。并且 ,当函数返回false时候,阻止接下来的render()函数的调用,阻止组件重渲染,而返回true时,组件照常重渲染。
我们对上面的情况做一个小小的改动:
import React from 'react'
class Test extends React.Component{constructor(props) {super(props);this.state = {Number:1}}//这里调用了setState但是并没有改变setState中的值handleClick = () => {const preNumber = this.state.Numberthis.setState({Number:this.state.Number})}//在render函数调用前判断:如果前后state中Number不变,通过return false阻止render调用shouldComponentUpdate(nextProps,nextState){if(nextState.Number == this.state.Number){return false}}render(){//当render函数被调用时,打印当前的Numberconsole.log(this.state.Number)return(<h1 onClick = {this.handleClick} style ={{margin:30}}>{this.state.Number}</h1>)}
}

点击标题1,UI仍然没有任何变化,但此时控制台已经没有任何输出了,没有意义的重渲染被我们阻止了!

组件的state没有变化,并且从父组件接受的props也没有变化,那它就还可能重渲染吗?——【可能!】

import React from 'react'
class Son extends React.Component{render(){const {index,number,handleClick} = this.props//在每次渲染子组件时,打印该子组件的数字内容console.log(number);return <h1 onClick ={() => handleClick(index)}>{number}</h1>}
}
class Father extends React.Component{constructor(props) {super(props);this.state = {numberArray:[0,1,2]}}//点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件handleClick = (index) => {let preNumberArray = this.state.numberArraypreNumberArray[index] += 1;this.setState({numberArray:preNumberArray})}render(){return(<div style ={{margin:30}}>{this.state.numberArray.map((number,key) => {return <Sonkey = {key}index = {key}number ={number}handleClick ={this.handleClick}/>})}</div>)}
}
export default Father

在这个例子中,我们在父组件Father的state对象中设置了一个numberArray的数组,并且将数组元素通过map函数传递至三个子组件Son中,作为其显示的内容(标题1,2,3),点击每个Son组件会更改对应的state中numberArray的数组元素,从而使父组件重渲染,继而导致子组件重渲染

demo:(点击前)

点击1后:

控制台输出:

demo如我们设想,但这里有一个我们无法满意的问题:输出的(1,1,2),有我们从0变到1的数据,也有未发生变化的1和2。这说明Son又做了两次多余的重渲染,但是对于1和2来说,它们本身state没有变化(也没有设state),同时父组件传达的props也没有变化,所以我们又做了无用功。

那怎么避免这个问题呢?没错,关键还是在shouldComponentUpdate这个钩子函数上

import React from 'react'
class Son extends React.Component{shouldComponentUpdate(nextProps,nextState){if(nextProps.number == this.props.number){return false}return true}render(){const {index,number,handleClick} = this.props//在每次渲染子组件时,打印该子组件的数字内容console.log(number);return <h1 onClick ={() => handleClick(index)}>{number}</h1>}
}
class Father extends React.Component{constructor(props) {super(props);this.state = {numberArray:[0,1,2]}}//点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件handleClick = (index) => {let preNumberArray = this.state.numberArraypreNumberArray[index] += 1;this.setState({numberArray:preNumberArray})}render(){return(<div style ={{margin:30}}>{this.state.numberArray.map((number,key) => {return <Sonkey = {key}index = {key}number ={number}handleClick ={this.handleClick}/>})}</div>)}
}
export default Father

这次只打印了数字发生改变的numberArray[0]对应的Son组件,说明numberArray[1],numberArray[2]的重渲染被“过滤”了!(goodjob!)

【注意】:nextProps.number == this.props.number不能写成nextProps == this.props,它总返回false因为它们是堆中内存不同的两个对象。(对比上面的红色的【注意】)
【总结】
一句话总结以上例子的结论:前后不改变state值的setState(理论上)和无数据交换的父组件的重渲染都会导致组件的重渲染,但你可以在shouldComponentUpdate这道两者必经的关口阻止这种浪费性能的行为

在这种简单的情景下,只要利用好shouldComponent一切都很美好,但是当我们的state中的numberArray变得复杂些的时候就会遇到很有意思的问题了,让我们把numberArray改成

[{number:0 /*对象中其他的属性*/},{number:1 /*对象中其他的属性*/},{number:2 /*对象中其他的属性*/}
]

这种对象数组的数据形式,整体的代码结构仍然不变:
import React from 'react'
class Son extends React.Component{shouldComponentUpdate(nextProps,nextState){if(nextProps.numberObject.number == this.props.numberObject.number){return false}return true}render(){const {index,numberObject,handleClick} = this.props//在每次渲染子组件时,打印该子组件的数字内容console.log(numberObject.number);return <h1 onClick ={() => handleClick(index)}>{numberObject.number}</h1>}
}
class Father extends React.Component{constructor(props) {super(props);this.state = {numberArray:[{number:0 /*对象中其他的属性*/},{number:1 /*对象中其他的属性*/},{number:2 /*对象中其他的属性*/}]}}//点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件handleClick = (index) => {let preNumberArray = this.state.numberArraypreNumberArray[index].number += 1;this.setState({numberArray:preNumberArray})}render(){return(<div style ={{margin:30}}>{this.state.numberArray.map((numberObject,key) => {return <Sonkey = {key}index = {key}numberObject ={numberObject}handleClick ={this.handleClick}/>})}</div>)}
}
export default Father

这个时候发现无论如何点击三个标题均无变化(没有数字改变),且控制台无输出!

what!!!我的代码结构明明没有任何变化啊,只是改传递数字为传递对象而已。嗯嗯,问题就出在这里,我们传递的是对象,关键在于nextProps.numberObject.number == this.props.numberObject.number这个判断条件,让我们思考,这与前面成功例子中的nextProps.number == this.props.number的区别:

1numberObject是一个对象
2.number是一个数字变量
3数字变量(number类型)和对象(Object类型)的内存存储机制不同
javascript变量分为基本类型变量和引用类型变量
对于number,string,boolean,undefined,null这些基本类型变量,值存在栈中:

对于object,Array,function这些引用类型变量,引用存在栈中,而不同的引用却可以指向堆内存中的同一个对象:

然后我们回过头再去看刚才的问题,在上面,nextProps.numberObject和this.props.numberObject的实际上指向的是同一个堆内存中的对象,所以点击标题时在多次判断条件中nextProps.numberObject.number==this.props.numberObject.number 等同于0 == 0 --> 1 == 1--> 2 == 2,所以总返回true,导致每次点击 调用shouldComponentUpdate()函数时都阻止了渲染,所以我们才看不到标题变化和控制台输出。
怎么才能保证每次取到不同的numberObject?
我们有三种方式:
1.ES6的扩展语法Object.assign()//react官方推荐的es6写法
2深拷贝/浅拷贝或利用JSON.parse(JSON.stringify(data))//相当于深拷贝,但使用受一定限制,具体的童鞋们可自行百度
3 immutable.js//react官方推荐使用的第三方库,目前github上20K star,足见其火热
4 继承react的PureComponent组件
1ES6的扩展语法Object.assign()
object.assign(TargetObj,obj1,obj2 ...)[返回值为Oject]可将obj1,obj2等组合到TargetObj中并返回一个和TargetObj值相同的对象,比如
 let obj = object.assign({},{a:1},{b:1})//obj为{a:1,b:1}

import React from 'react'
class Son extends React.Component{shouldComponentUpdate(nextProps,nextState){//旧的number Object对象的number属性 == 新的number Object对象的number属性if(nextProps.numberObject.number == this.props.numberObject.number){console.log('前一个对象' + JSON.stringify(nextProps.numberObject)+'后一个对象' + JSON.stringify(this.props.numberObject));return false}return true}render(){const {index,numberObject,handleClick} = this.props//在每次渲染子组件时,打印该子组件的数字内容console.log(numberObject.number);return <h1 onClick ={() => handleClick(index)}>{numberObject.number}</h1>}
}
class Father extends React.Component{constructor(props) {super(props);this.state = {numberArray:[{number:0 /*对象中其他的属性*/},{number:1 /*对象中其他的属性*/},{number:2 /*对象中其他的属性*/}]}}//点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件handleClick = (index) => {let preNumberArray = this.state.numberArray//把做修改的number Object先拷贝到一个新的对象中,替换原来的对象preNumberArray[index] = Object.assign({},preNumberArray[index])//使新的number Object对象的number属性加一,旧的number Object对象属性不变preNumberArray[index].number += 1;this.setState({numberArray:preNumberArray})}render(){return(<div style ={{margin:30}}>{this.state.numberArray.map((numberObject,key) => {return <Sonkey = {key}index = {key}numberObject ={numberObject}handleClick ={this.handleClick}/>})}</div>)}
}
export default Father

点击0后打印1,问题解决!

2深拷贝/浅拷贝或利用JSON.parse(JSON.stringify(data))

在这里先不多介绍了,大家可自行百度...
3immutable.js —— react官方推荐的第三方库:
先让我们回到困扰我们的问题的根源 —— 两个引用类型变量的赋值表达式和两个基本类型变量的赋值表达式不同。
对于基本类型变量a和b, b = a 后,访问a,b相当于访问两个不同的变量,两者彼此毫无关联
let a =2,b;
b = a;//将a的值赋给b
a = 1;//改变a的值
console.log('a =' + a);//输出 a = 1
console.log('b =' + b);//输出 b = 2,表明赋值后b,a毫无关联

对于引用类型变量obj1和obj2,obj1 = obj2后,访问obj1和obj2相当于访问同一个变量,两者形成了一种“耦合”的关系
let obj1 ={name:'李达康'},obj2;
obj2 = obj1;//将obj1的地址赋给obj2
obj1.name = '祁同伟';//改变obj1的name属性值
console.log('obj1.name =' + obj1.name);//输出 obj1.name = '祁同伟'
console.log('obj2.name =' + obj2.name);//输出 obj2.name = '祁同伟',表明赋值后obj1/obj2形成耦合关系,两者互相影响

为什么基本类型和引用类型在变量赋值上面存在这么大的不同呢?因为基本类型变量占用的内存很小,而引用类型变量占用的内存比较大,几个引用类型变量通过指针共享同一个变量可以节约内存
所以,在这个例子中,我们上面和下面所做的一切,都是在消除对象赋值表达式所带来的这一负面影响
那我们能不能通过一些方式,使得preNumberArray = this.state.numberArray的时候,两变量指向的就是不同的两个对象呢?于是这里就引入了一个强大的第三方库 ——immutable.js,先举个例子示范一下:
(首先要通过npm install immutable 安装immutable的依赖包哦)
const { fromJS } = require('immutable')
let obj1 = fromJS({name:'李达康'}),obj2;
obj2 = obj1;//obj2取得与obj1相同的值,但两个引用指向不同的对象
obj2 = obj2.set('name','祁同伟');//设置obj2的name属性值为祁同伟
console.log('obj1.name =' + obj1.get('name'));//obj1.name =李达康
console.log('obj2.name =' + obj2.get('name'));//obj2.name =祁同伟

【注意】

1这个时候obj1=obj2并不会使两者指向同一个堆内存中的对象了!所以这成功绕过了我们前面的所提到的对象赋值表达式所带来的坑。所以我们可以随心所欲地像使用普通基本类型变量复制 (a=b)那样对对象等引用类型赋值(obj1 = obj2)而不用拷贝新对象

2对于immutable对象,你不能再用obj.属性名那样取值了,你必须使用immuutable提供的API

  • fromJS(obj)把传入的obj封装成immutable对象,在赋值给新对象时传递的只有本身的值而不是指向内存的地址。
  • obj.set(属性名,属性值)给obj增加或修改属性,但obj本身并不变化,只返回修改后的对象
  • obj.get(属性名)从immutable对象中取得属性值

1优点:深拷贝/浅拷贝本身是很耗内存,而immutable本身有一套机制使内存消耗降到最低

2缺点:你多了一整套的API去学习,并且immutable提供的set,map等对象容易与ES6新增的set,map对象弄混

让我们一试为快:
import React from 'react'
const { fromJS } = require('immutable')
class Son extends React.Component{shouldComponentUpdate(nextProps,nextState){//旧的number Object对象的number属性 == 新的number Object对象的number属性if(nextProps.numberObject.get('number') == this.props.numberObject.get('number')){return false}return true}render(){const {index,numberObject,handleClick} = this.propsconsole.log(numberObject.get('number'));//在每次渲染子组件时,打印该子组件的数字内容return <h1 onClick ={() => handleClick(index)}>{numberObject.get('number')}</h1>}
}
class Father extends React.Component{constructor(props) {super(props);this.state = {numberArray:fromJS([{number:0 /*对象中其他的属性*/},{number:1 /*对象中其他的属性*/},{number:2 /*对象中其他的属性*/}])}}//点击后使numberArray中数组下标为index的数字值加一,重渲染对应的Son组件handleClick = (index) => {let preNumberArray = this.state.numberArray//使新的number Object对象的number属性加一,旧的number Object对象属性不变let newNumber = preNumberArray.get(index).get('number') + 1;preNumberArray = preNumberArray.set(index,fromJS({number: newNumber}));this.setState({numberArray:preNumberArray})}render(){return(<div style ={{margin:30}}>{this.state.numberArray.map((numberObject,key) => {return <Sonkey = {key}index = {key}numberObject ={numberObject}handleClick ={this.handleClick}/>})}</div>)}
}
export default Father

成功,demo效果同上

这篇文章实在太过冗长,不过既然您已经看到这里了,那么我就介绍解决上述问题的一种简单粗暴的方法——

4继承react的PureComponent组件

如果你只是单纯地想要避免state和props不变下的冗余的重渲染,那么react的pureComponent可以非常方便地实现这一点:

import React, { PureComponent } from 'react'
class YouComponent extends PureComponent {
render() {
// ...
}
}

当然了,它并不是万能的,由于选择性得忽略了shouldComponentUpdate()这一钩子函数,它并不能像shouldComponentUpdate()“私人定制”那般随心所欲

转载于:https://www.cnblogs.com/nerosen/p/9952193.html

react生命周期与优化相关推荐

  1. react生命周期(自己的方式理解)

    react生命周期(自己的方式理解) react的生命周期函数有:挂载,渲染/更新,卸载 首先要知道react生命周期最初始的要有哪些?最后生命中出现的又有哪些?我本人喜欢把react比作是一个人的一 ...

  2. ES6中的React生命周期详解

    太长时间没写react了,有点生.重新捡起来练练手. import React,{ Component } from 'react';class Demo extends Component {con ...

  3. react生命周期方法介绍

    react生命周期 react生命周期主要包括三个阶段:初始化阶段.运行中阶段.销毁阶段 react在不同的生命周期会触发不同的钩子函数 初始化阶段 getDefaultProps() 设置组件默认的 ...

  4. react生命周期详细介绍

    目录 挂载:在组件实例被创建并插入到dom中时,生命周期调用顺序如下 constructor componentWillMount getDerivedStateFromProps render co ...

  5. React、react生命周期、react生命周期运行过程、shouldComponentUpdate的用法

    React.react生命周期.react生命周期运行过程 1.创建阶段 constructor(){super()console.log('执行构造函数')this.state={num:1}}UN ...

  6. React生命周期讲解

    React生命周期图解 先上图 其实总结就是 进入当前页面,,然后渲染页面,加载数据,渲染demo,数据更新,组件卸载 constructor /* * constructor 其实是Es6 里面带的 ...

  7. React生命周期大全:

    react生命周期分三个阶段: 1.初始化阶段(挂载) a.componentWillMount (): -- 用的较少,组件挂载到DOM前调用,且只会被调用一次,它代表的过程是组件已经经历了cons ...

  8. React生命周期(经典)

    什么是生命周期函数? && 生命周期函数(钩子函数)通俗的说就是在某一时刻会被自动调用执行的函数. React生命周期(经典) 1.挂载 componentWillMount ,组件将 ...

  9. React生命周期理解

    前言 如果将React的生命周期比喻成一只蚂蚁爬过一根吊绳,那么这只蚂蚁从绳头爬到绳尾,就会依次触动不同的卡片挂钩.在React每一个生命周期中,也有类似卡片挂钩的存在,我们把它称之为'钩子函数'.那 ...

最新文章

  1. 把PPT做漂亮点真的有用!图表美观能增加可信度!作者:还会影响论文引用和通过率...
  2. __asm__ __volatile__(: : :memory)
  3. 中国增材制造市场投资前景与发展投资战略调研报告2022-2028年版
  4. matlab 调用opencv,matlab调用opencv (mac 或 linux)
  5. 外网ip怎么查_无公网IP的情况下,搞定群晖并实现远程Nas访问
  6. Web socket广播
  7. 阿里强化学习入选MIT十大突破 “新技术”
  8. 懒人模式Singleton模式Meyers版本号
  9. C++新特性探究(五):for循环、范围for语句
  10. asp.net core 2.1 增加Nlog日志到sql server数据库
  11. vue(vue-cli+vue-router)+babel+webpack项目搭建入门 (第二步)
  12. wxpython安装linux_Ubuntu中安装wxPython
  13. Java零基础入门基础视频教程
  14. Mac pro 安装ubuntu系统
  15. Oracle后台进程及其作用简介
  16. mysql 删除临时表的语句_MySQL如何创建和删除临时表_MySQL
  17. CMD批处理实现dot命令自动运行更新
  18. 有用的“歪门邪道”-设计模式
  19. 商业智能时代,大数据分析行业前景
  20. 新生儿取名:撩人于无形的女宝宝名字,任你挑选

热门文章

  1. 杜克大学计算机科学博士,杜克大学计算机科学CS博士学位申请介绍
  2. 复盘2020年全球科技行业:5G建设加速、半导体行业洗牌、云计算爆发 | TMT观察...
  3. 精美 咖啡品牌logo设计灵感 标志设计
  4. Android——Android app bundle插件化部署
  5. Hibernate高级映射技术(一)自定义数据类型StringList (转载用于收藏)
  6. 单播通信、主播通信和广播通信
  7. 【电子签名手机-vue】
  8. 10 个免费的网络监控工具
  9. Qt - WPS文本编辑器(WPS段落对齐)
  10. 来自一个入行三年半的大数据练习生自述