欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解react阻止无效重渲染的多种方式

程序员文章站 2022-03-04 13:18:57
在开发react组件的过程中,我们经常会遇到这个问题:什么情况下组件会重新渲染? 当内部data发生改变,state发生改变(通过调用this.setstate()) 以...

在开发react组件的过程中,我们经常会遇到这个问题:什么情况下组件会重新渲染?

当内部data发生改变,state发生改变(通过调用this.setstate()) 以及父组件传过来的props发生改变时,会导致组件重新渲染。

以下几个问题同样值得我们思考:

setstate()函数在任何情况下都会导致组件重渲染吗?如果setstate中的state没有发生改变呢?

如果state和从父组件传过来的props都没变化,那他就一定不会发生重渲染吗?

首先,我们来解决这两个问题

没有导致state的值发生变化的this.setstate()是否会导致重渲染  --- 

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}>
       {this.state.number}
      </h1>)
 }
}

从控制台的打印结果可以看出:共打印了15次1,但是组件并没有发生任何变化!!!

这样的结果不是我们想要的,如何阻止组件的重渲染呢?这时我们想到了react的一个生命周期钩子 shouldcomponentupdate

react生命周期中有这样一个钩子,叫shouldcomponentupdate函数,是重渲染时render()函数调用前被调用的函数,

两个参数 nextprops和nextstate ,分别表示下一个props和state的值。

当函数返回false时,阻止接下来的render()函数的调用,阻止组件重渲染,返回true时,组件照常渲染

 //加入shouldcomponentupdate钩子
//在render函数调用前判断:如果前后state中number不变,通过return false阻止render调用
 shouldcomponentupdate(nextprops,nextstate){
   if(nextstate.number == this.state.number){
    return false
   }
 }

加入上述代码后,打开控制台,点击按钮,还是白白的,说明无效的重渲染被我们阻止了

第二个问题,组件的state和从父组件传递过来的props都没改变,组件还会重渲染吗 --- 可能

同样可以通过shouldcomponentupdate钩子进行阻止

所以说,前后不改变state的值的setstate和无数据交换的父组件的重渲染都会导致组件的重渲染,但我们可以通过shouldcomponentupdate来阻止这两种情况

shouldcomponentupdate并不是完美的,只能阻止扁平的对象

nextstate.number == this.state.number

如果调用层次比较深

nextstate.numberobject.number == this.state.numberobject.number

number 是一个数字变量

numberobject是一个对象

数字变量(number类型)和对象(object)类型的内存存储机制不同

这时候,因为两者都指向堆中的同一个对象,所以一直都是true  shouldcomponentupdate失效了

js变量分为基本类型的变量和引用类型的变量

对于number,string,boolean,undefined,null这些基本类型变量,值存在栈中

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

那么,问题就来了

怎么样才能取到不同的numberobject呢?

四种方法:

1、es6的扩展语法object.assign()

2、深拷贝/浅拷贝或利用json.parse(json.stringify(data))相当于深拷贝,但使用受一定限制

3、引入immutable.js react官方推荐的第三方库

4、继承react的purecomponent组件(代替component)

在js中,引用类型的数据,优点在于频繁的操作数据都是在原对象的基础上修改,不会创建新对象,从而可以有效的利用内存,不会浪费内存,这种特性称为mutable(可变),但恰恰它的优点也是它的缺点,太过于灵活多变在复杂数据的场景下也造成了它的不可控性,假设一个对象在多处用到,在某一处不小心修改了数据,其他地方很难预见到数据是如何改变的,针对这种问题的解决方法,一般就像刚才的例子,会想复制一个新对象,再在新对象上做修改,这无疑会造成更多的性能问题以及内存浪费。

为了解决这种问题,出现了immutable对象,每次修改immutable对象都会创建一个新的不可变对象,而老的对象不会改变。

immutable.js主要有三大特性:

persistent data structure (持久化数据结构)

structural sharing (结构共享)

support lazy operation (惰性操作)

immutable data 就是一旦创建,就不能再被更改的数据。对 immutable 对象的任何修改或添加删除操作都会返回一个新的 immutable 对象。immutable 实现的原理是 persistent data structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepcopy 把所有节点都复制一遍带来的性能损耗,immutable 使用了 structural sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享

三个最重要的数据结构: map list set

map:键值对集合,对应于 object,es6 也有专门的 map 对象

list:有序可重复的列表,对应于 array

set:无序且不可重复的列表

//map() 原生object转map对象 (只会转换第一层,注意和fromjs区别)
immutable.map({name:'danny', age:18})

//list() 原生array转list对象 (只会转换第一层,注意和fromjs区别)
immutable.list([1,2,3,4,5])

//fromjs()  原生js转immutable对象 (深度转换,会将内部嵌套的对象和数组全部转成immutable)
immutable.fromjs([1,2,3,4,5])  //将原生array --> list
immutable.fromjs({name:'danny', age:18})  //将原生object --> map

//tojs() immutable对象转原生js (深度转换,会将内部嵌套的map和list全部转换成原生js)
immutabledata.tojs();

//查看list或者map大小 
immutabledata.size 或者 immutabledata.count()

// is()  判断两个immutable对象是否相等
immutable.is(ima, imb);

//merge() 对象合并
var ima = immutable.fromjs({a:1,b:2});
var ima = immutable.fromjs({c:3});
var imc = ima.merge(imb);
console.log(imc.tojs()) //{a:1,b:2,c:3}

对于两个一样的数据,只有通过equals进行比较才是相等的  ==  ===都不行

如果 某个是另一个克隆出来的,那么全部都相等

push添加 unshift在头部添加 concat组合  返回的是新数据,而不是数据的长度

//增删改查(所有操作都会返回新的值,不会修改原来值)
var immutabledata = immutable.fromjs({
  a:1,
  b:2,
  c:{
    d:3
  }
});
var data1 = immutabledata.get('a') // data1 = 1 
var data2 = immutabledata.getin(['c', 'd']) // data2 = 3  getin用于深层结构访问
var data3 = immutabledata.set('a' , 2);  // data3中的 a = 2
var data4 = immutabledata.setin(['c', 'd'], 4);  //data4中的 d = 4
var data5 = immutabledata.update('a',function(x){return x+4}) //data5中的 a = 5
var data6 = immutabledata.updatein(['c', 'd'],function(x){return x+4}) //data6中的 d = 7
var data7 = immutabledata.delete('a')  //data7中的 a 不存在
var data8 = immutabledata.deletein(['c', 'd'])  //data8中的 d 不存在复制代码

优点:

  • 降低mutable带来的复杂度
  • 节省内存
  • 历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
  • 拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便

缺点:

  • 需要重新学习api
  • 资源包大小增加(源码5000行左右)
  • 容易与原生对象混淆:由于api与原生不同,混用的话容易出错。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。