jjzjj

react性能优化之memo的作用和memo的坑

工边页字 2023-09-09 原文

前言

在react中,组件渲染的是最常有的事情。但是,有部分的渲染是不必要的,是可以避免的。

在react的一般规则中,只有父组件的某一个状态改变,父组件下面所有的子组件不论是否使用了该状态,都会进行重新渲染。

显然,对于没有用到被改变的那个状态的组件来说,重新渲染是完全没有必要的。所以,React.memo就诞生了。

父组件中状态的改变会让所有的子组件重新渲染

举个例子 ↓

上面的例子中,我们有两个state,一个buibuibui,一个tututu。被传入children组件的是tututu,在父组件中改变的是buibuibui。

问:当父组件的buibuibui这个state被改变的时候,只接收了tututu这个变量的children(子组件)会被重新渲染吗。

答:会的,只要父组件的状态改变,所有的子组件不论是否使用到了被改变的那个state都会被重新渲染。

如图↓

显然这种渲染是完全没必要的。我又没有使用被改变的那个state,我本身本身也没有什么视图需要更新,根本没必要重新渲染。

在阻止重新渲染这个需求的基础上,诞生了memo(),memo是react的一种缓存技术,这个函数可以检测从父组件接收的props,并且在父组件改变state的时候比对这个state是否是本组件在使用,如果不是,则拒绝重新渲染。

memo 和它的使用方式

并且它的使用方法也非常的简单。只需要在把子组件当成这个函数的入参包起来就好了。

例如↓

像这样,这Children就是被缓存成功了。下次当父组件中无关它的state(状态)被更新时候,Children组件就不会重新渲染。

参考上面的例子去介绍memo()就是

子组件被memo函数保护了。当父组件中一切无关子组件的state被改变时,子组件拒绝重新渲染,这样节省了性能。

而上面的例子中,传入Children组件的是tututu这个状态。而父组件中被改变的是buibuibui这个state。buibuibui被改变与Children无关,因为children被memo保护,所以chidren不重新渲染

反之,如果children组件没有被memo保护,那么即使被改变的buibuibui这个state与children组件无关。children组件也会被重新渲染。

总而言之就是,如果当前组件被memo保护,那么当前组件的props不变,则组件不进行重新渲染。这样,我们合理的使用memo就可以为我们的项目带来很大的性能优化。

以上,就是关于react的组价缓存。

memo的坑

上面说了memo的用处和好处。
下面我们来说memo坑的地方

第一个坑

有那么一种情况,被memo的保护的组件即使props变了,它也不会重新渲染。
当被改变的那个props是一个数组(对象)的时候
例如↓

上图中,传入了一个list数组进去子组件,子组件内部是被memo缓存了的。这个时候,如果我们往list这个数组中push()一个6,那么子组件中的props改变了,理论上来说,子组件应该重新渲染了。但实际上并不会。

这是为什么呢?因为memo的保护是对props做一个浅比较不了解什么是浅比较的同学点这里

而数组的使用push()方法看似是变了。但变的只是堆中的数据,存在与栈中的地址依然不会改变。memo是检测不到的。所以,使用push等不能返回一个新数组的方法,均无法触发memo的更新机制。

如图↓

想要改变数组也能让子组件更新,有方法。
就像刚刚说的,需要让memo检测到数组栈地址的变化。要栈地址变化的话,只要返回一个全新的数组就好了。

所以,我们不妨将代码做出以下修改。

	const [list,setList] = useState([1,2,3,4,5]);
	
	setList(list.push(1)); //这样是不会被memo检测到的,是无法触发memo更新的
	
	setList([...list,1]); //这样才可以,创建一个新数组,再在里面解构旧数组,往后面追加 1
	//这样,就等于返回了一个新的数组,栈中的地址就会改变,memo就可以检测到并触发更新

这样,就可以既修改数组,又触发组件更新了

第二个坑

对!还有第二个坑!!

memo是不是很好用?是不是有那么好的东西恨不得每个组件都包一下?

这就是它坑的地方了,它不能每个组件都包一下。

倒不是说全包就报错,只是如果全包的话,还不如不包。

话说回来,如果真的每个组件都有被缓存的必要而且不会给项目带来破坏性问题,为什么react不直接把memo设为默认的呢。当然是因为每个都缓存的话,会给项目带来毁灭性的问题咯。

我们需要知道的是,缓存也需要成本。如果每个组件都进行缓存,会给浏览器带来非常非常大的负担。

所以在平常项目中,我们需要挑选一些经常被使用,经常会被重新渲染的组件去有目标的缓存他。而不是每一个组件都缓存一下。切记切记。

总结

  • 父组件中state(状态)改变,不受memo保护的子组件也会重新渲染
  • memo会检测props到改变来决定组件是否需要进行重新渲染,换言之就是,被memo函数包起来的组件只有本身的props被改变之后才会重新渲染
  • memo只能进行浅拷贝来校验决定是否触发重新渲染。所以改变数组(对象)的props时候记得返回一个全新的数组(对象)
  • memo不是项目中所有的组件都需要包一下。包的太多反而会起反效果,我们需要选择那些经常被重新渲染的组件有选择性的去缓存。

有关react性能优化之memo的作用和memo的坑的更多相关文章

  1. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  2. ruby-on-rails - "assigns"在 Ruby on Rails 中有什么作用? - 2

    我目前正在尝试学习RubyonRails和测试框架RSpec。assigns在此RSpec测试中做什么?describe"GETindex"doit"assignsallmymodelas@mymodel"domymodel=Factory(:mymodel)get:indexassigns(:mymodels).shouldeq([mymodel])endend 最佳答案 assigns只是检查您在Controller中设置的实例变量的值。这里检查@mymodels。 关于ruby-o

  3. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  4. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  5. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  6. ruby - 字符串文字前面的 * 在 ruby​​ 中有什么作用? - 2

    这段代码似乎创建了一个范围从a到z的数组,但我不明白*的作用。有人可以解释一下吗?[*"a".."z"] 最佳答案 它叫做splatoperator.SplattinganLvalueAmaximumofonelvaluemaybesplattedinwhichcaseitisassignedanArrayconsistingoftheremainingrvaluesthatlackcorrespondinglvalues.Iftherightmostlvalueissplattedthenitconsumesallrvaluesw

  7. ruby-on-rails - Ruby 长时间运行的进程对队列事件使用react - 2

    我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby​​脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几

  8. ruby - 为什么这个 eval 在 Ruby 中不起作用 - 2

    你能解释一下吗?我想评估来自两个不同来源的值和计算。一个消息来源为我提供了以下信息(以编程方式):'a=2'第二个来源给了我这个表达式来评估:'a+3'这个有效:a=2eval'a+3'这也有效:eval'a=2;a+3'但我真正需要的是这个,但它不起作用:eval'a=2'eval'a+3'我想了解其中的区别,以及如何使最后一个选项起作用。感谢您的帮助。 最佳答案 您可以创建一个Binding,并将相同的绑定(bind)与每个eval相关联调用:1.9.3p194:008>b=binding=>#1.9.3p194:009>eva

  9. ruby-on-rails - Spring 不起作用。 [未初始化常量 Spring::SID::DL] - 2

    我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li

  10. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

随机推荐