react div onclick叠加_小前端读源码 - React(浅析Keys原理)

a739a6a93a81f138b4bd09c34035ddcc.png

在使用React的时候,我们经常无法避免使用循环去渲染元素。例如我们有一个商品列表,我们就需要根据后端提供的接口(一般是一个数组)循环渲染出商品信息。在渲染的商品组件中,如果不填写一个key给循坏渲染的组件,那么React将会提示一个警告。

c2e69f0720be5a5f892161dbf51f57e4.png

在React的官网文档中有说道,循坏渲染组件需要为组件添加一个兄弟组件之间唯一的key作为标识。

列表 & Keys - React​react.docschina.org
f2ae7af934517cb33c275a03508b6f30.png

相信很多人都知道,React会根据这个key去决定是否重复使用组件。那么我们就看看在React内部,他是如何去判断这个Key,以及如何去重用组件的。


本篇文章都会基于以下demo展开:

class 

React是什么时候验证我们的循环渲染组件没有添加keys呢?

首先我们编写循环渲染的组件一边都是这样写的:

this

经过babel编译之后是这样的:

_createClass

在初次渲染的时候,会对App类的div进行实例,通过react.createElement对App类的div转为一个ReactDOM对象。在转换的时候,会对div的children也转化,当碰到map渲染的时候,那么div的其中一个children的类型就为数组了,那么在转换div的时候发现有其中一个children是一个数组,那么React就会对数组进行验证是否带有keys。

function 

React是如何利用Keys的?

我们修改一下demo。

class 

在demo中我们先不为每个item添加key。

464be98ee80417dc96dbb6e582c60c9d.png

我先填入一些数据。

c19a12b60f0a26bb8da2f3ee587da7ff.png

点击setState。

cc45aca8503c77ceec266d1c7120d984.png

不知道大家发现问题没有,顺序是调转了,但是input的内容并没有根据顺序变化而变化,还是没有改变顺序。

如果我们为每个循环渲染的组件叫上key,在进行顺序变化会发现input也会跟着顺序变化。

b8dccc7a07f4a9e51231531af8556499.png

这是为什么呢?通过阅读源码以及断点查看,我们看看带上key的组件在改变顺序后重新渲染会是如何进行的。

首先在beginWork的时候可以看到,因为当前处理的Fiber节点是一个数组,所以会当成Fragment来进行处理。通过断点观看,可以看到传入的组件位置已经根据state的不同进行了修改。

5d020dec9458c7f8bfd419b131ce834f.png

可以看到当前数组的child还没有发生变化!

4d5e952f60949fb92f54ef393980fa2e.png

当前的workInProgress.child是key为a1的div。

React会对当前数组进行第一次循环,获取每个子节点的key值生成一个Set数据knownKeys

{
      

接着react会调用updateSlot函数,会对旧的数组的第一个子元素和新数组的第一个子元素传入进行对比。

{
    

接着react会调用mapRemainingChildren函数。

function 

从这里我们知道,其实就算我们不传入key,在更新的时候,React也会帮我们设置key到对应的元素中。

然后进入另外一个循环,这个循环会循环执行updateFromMap函数,分别会传入existingChildren(根据旧数组得出的Map数据), returnFiber, newIdx, newChildren[newIdx](新数组中,当前下标的子节点), expirationTime这些参数。

React会根据旧数据中当前循环的item和新数据的item进行对比,最终决定如何更新。

function 

这样React就完成了对新旧数组的顺序替换,原先数组Fiber节点的child是key为a1的Fiber节点,a1的sibling节点是key为a2的节点。通过一系列的转换后,最后返回出给数组Fiber节点的child是key为a2的Fiber节点,而key为a2的sibling节点是key为a1的Fiber节点。

同时因为key为a1和a2的Fiber所传入新的prosp并没有改变,所以在diff中,并不会对它们有任何的更新。

同时因为Fiber节点的位置交换,所以Fiber节点下的所有Fiber子节点(包括文字和input标签)都会自动替换位置。所以在最终渲染的时候,子节点会跟随带有key的父节点一起移动位置。


之前demo中有提到一个问题,就是当我们没有为数组中的子元素提供key属性时,修改顺序的时候,input并没有跟随父节点一起移动。

首先在进入到updateSlot函数的时候,因为新旧的子元素的key都等于null,因此React会把它当做是同一个节点,所以并不会对节点的位置进行改变,只会更新props到对应的Fiber节点中。因此在改变state的时候,文字从1变成了2,但是input因为没有任何改变,所以不做更新。因此才会出现input没有跟随父节点改变位置。因为从Fiber节点的角度来说,就没有改变过位置,只是因为传入的文字不一样,导致text的Fiber节点更新了内容,导致我们的一个错觉罢了。

总结

  1. React就在渲染数组时如果子组件没有提供key,会默认将循环的index作为key来用作第一次渲染。
  2. React的key的作用就是在setState的render阶段,对Fiber节点尽可能的重用。
  3. 在渲染数组时,尽可能不要改变子节点的标签类型,例如原本是div尽可能不要变成其他标签,因为改变了标签类型,Fiber节点将需要重新生成,并不能起到复用的效果。
  4. key只需要在当前数组中唯一即可,不需要担心全局的问题。

如果不太清楚中间渲染过程可以参考一下之前的源码阅读文章帮助理解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值