shouldComponentUpdate不能直接比较object
凡是参阅过react官方英文文档的童鞋大体上都能知道对于一个组件来说,其state的改变(调用this.setState()方法)以及从父组件接受的props发生变化时,会导致组件重渲染,正所谓"学而不思则罔",在不断的学习中,我开始思考这一些问题:
import React from 'react'
class Test extends React.Component{
constructor(props) {
super(props);
this.state = {
Number:1//设state中Number值为1
}
}
//这里调用了setState但是并没有改变setState中的值
handleClick = () => {
const preNumber = this.state.Number
this.setState({
Number:this.state.Number
})
}
render(){
//当render函数被调用时,打印当前的Number
console.log(this.state.Number)
return(<h1 onClick = {this.handleClick} style ={{margin:30}}>
{this.state.Number}
</h1>)
}
}
export default Test
//省略reactDOM的渲染代码...
import React from 'react'
class Test extends React.Component{
constructor(props) {
super(props);
this.state = {
Number:1
}
}
//这里调用了setState但是并没有改变setState中的值
handleClick = () => {
const preNumber = this.state.Number
this.setState({
Number:this.state.Number
})
}
//在render函数调用前判断:如果前后state中Number不变,通过return false阻止render调用
shouldComponentUpdate(nextProps,nextState){
if(nextState.Number == this.state.Number){
return false
}
}
render(){
//当render函数被调用时,打印当前的Number
console.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.numberArray
preNumberArray[index] += 1;
this.setState({
numberArray:preNumberArray
})
}
render(){
return(<div style ={{margin:30}}>{
this.state.numberArray.map(
(number,key) => {
return <Son
key = {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.numberArray
preNumberArray[index] += 1;
this.setState({
numberArray:preNumberArray
})
}
render(){
return(<div style ={{margin:30}}>{
this.state.numberArray.map(
(number,key) => {
return <Son
key = {key}
index = {key}
number ={number}
handleClick ={this.handleClick}/>
}
)
}
</div>)
}
}
export default Father
在这种简单的情景下,只要利用好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.numberArray
preNumberArray[index].number += 1;
this.setState({
numberArray:preNumberArray
})
}
render(){
return(<div style ={{margin:30}}>{
this.state.numberArray.map(
(numberObject,key) => {
return <Son
key = {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的区别:
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 <Son
key = {key}
index = {key}
numberObject ={numberObject}
handleClick ={this.handleClick}/>
}
)
}
</div>)
}
}
export default Father
点击0后打印1,问题解决!
2深拷贝/浅拷贝或利用JSON.parse(JSON.stringify(data))
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毫无关联
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形成耦合关系,两者互相影响
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.props
console.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 <Son
key = {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()“私人定制”那般随心所欲
具体代码就不放了
shouldComponentUpdate不能直接比较object的更多相关文章
- 自适应高度文本框 react contenteditable
import React, { Component } from 'react'; import PropTypes from 'prop-types'; const reduceTargetKeys ...
- 正式学习 React(三)番外篇 reactjs性能优化之shouldComponentUpdate
性能优化 每当开发者选择将React用在真实项目中时都会先问一个问题:使用react是否会让项目速度更快,更灵活,更容易维护.此外每次状态数据发生改变时都会进行重新渲染界面的处理做法会不会造成性能瓶颈 ...
- 【学而思】利用shouldComponentUpdate钩子函数优化react性能以及引入immutable库的必要性
凡是参阅过react官方英文文档的童鞋大体上都能知道对于一个组件来说,其state的改变(调用this.setState()方法)以及从父组件接受的props发生变化时,会导致组件重渲染,正所谓&qu ...
- 【react】利用shouldComponentUpdate钩子函数优化react性能以及引入immutable库的必要性
凡是参阅过react官方英文文档的童鞋大体上都能知道对于一个组件来说,其state的改变(调用this.setState()方法)以及从父组件接受的props发生变化时,会导致组件重渲染,正所谓&qu ...
- react与mox-react的shouldComponentUpdate 理解
react性能优化中,提到的就是通过 React.PureComponent 替换 React.Component 组件进行编程. 两个组件之间的不同主要就是PureComponent做了should ...
- react如何通过shouldComponentUpdate来减少重复渲染
转自:https://segmentfault.com/a/1190000016494335 在react开发中,经常会遇到组件重复渲染的问题,父组件一个state的变化,就会导致以该组件的所有子组件 ...
- CoreCLR源码探索(一) Object是什么
.Net程序员们每天都在和Object在打交道 如果你问一个.Net程序员什么是Object,他可能会信誓旦旦的告诉你"Object还不简单吗,就是所有类型的基类" 这个答案是对的 ...
- JavaScript Object对象
目录 1. 介绍:阐述 Object 对象. 2. 构造函数:介绍 Object 对象的构造函数. 3. 实例属性:介绍 Object 对象的实例属性:prototype.constructor等等. ...
- javascript之Object.defineProperty的奥妙
直切主题 今天遇到一个这样的功能: 写一个函数,该函数传递两个参数,第一个参数为返回对象的总数据量,第二个参数为初始化对象的数据.如: var o = obj (4, {name: 'xu', age ...
随机推荐
- Dalvik虚拟机总结
一.Dalvik虚拟机启动 在启动Zygote进程时,会启动Dalvik虚拟机,完毕以下几件事: 1. 创建了一个Dalvik虚拟机实例: 2. 载入了Java核心类及注冊其JNI方法: 3. 为主线 ...
- windows bat命令 开启关闭Oracle服务
0.吐槽 单位发的ThinkPad T61.太弱小了. 问题是我去百度下T61,发现它好贵好贵.真心无力吐槽.还不如给我发台外星人,廉价点的. . Oracle一开就内存就不够了.所以绝对不能让它开机 ...
- C#文件运行类的VB.NET版本号
主要差别在于事件处理要採用AddHandler和RemoveHandler,以及AddressOf三个keyword,其他基本一样. VB的操作稍微繁琐.但仍然能够实现.
- html切换效果
1. 使用方式 <meta HTTP-EQUIV="Page-Enter" CONTENT="revealtrans(duration=1.0, transitio ...
- Solr基础教程之solrconfig.xml(三)
前面介绍过schema.xml的一些配置信息,本章介绍solrconfig.xml的配置,以及怎样安装smartcn分词器和IK分词器,并介绍主要的查询语法. 1. solr配置solrconfig. ...
- Generic Interfaces (C# Programming Guide)
https://msdn.microsoft.com/en-us/library/kwtft8ak(v=vs.140).aspx It is often useful to define interf ...
- 【转】Android HTML5 Video视频标签自动播放与自动全屏问题解决
为了解决 HTML5Video视频标签自动播放与全屏问题,在网上找了很多相关资料,网上也很多关于此问题解决方法,但几乎都不能解决问题,特别对各大视频网站传回来的html5网页视频自动播放与全屏问题,我 ...
- python pass 的用法
python pass用法 1.空语句 do nothing 2.保证格式完整 3.保证语义完整 4.以if语句为例: C/C++中写法: if(true) ; // do nothing else ...
- P3567 [POI2014]KUR-Couriers 主席树
这个题比一般主席树还要简单,但是用来练习主席树再好不过了,在这里我再放一下主席树板子. 代码: #include<iostream> #include<cstdio> #inc ...
- B1299 [LLH邀请赛]巧克力棒 博弈论
这个题一看就是nim游戏的变形.每次先手取出巧克力就是新建一个nim,但假如先手取一个为0的而且无论后手怎么取剩下的都无法为零就行了.然后用dfs跑. 题干: Description TBL和X用巧克 ...