我之前遇到过一个设计师开的bug,是说当用户在一个列表里增加了一行记录时,界面上的 CheckBox 异样地执行了一个动画成果。接到这个bug时第一反馈是看看 CheckBox 对应的布尔值有没有发生变化,确认没有之后,我感觉这须要考察一下控件CSS的实现逻辑了,所以这个bug就被我放在一边了,因为没有影响到数据,被认为是低优先级的问题。直到起初有机会去解决手上残留的bug,才再次尝试解决。

问题形容

我用一个例子去形容过后我遇到的问题。假如咱们用一个列表去实现某个业务上的性能,列表的每一行最右面是个 CheckBox,右面有一些文本信息。列表还反对减少行,按一下按钮(下图中的 Add to top),列表的最上方会加上一行。界面像是上面这样(临时疏忽那个Reverse按钮)。

我在写这里的代码时牵强附会地把这个列表用一个数组作为数据源,而后用 map 函数返回JSX去创立这个列表,因为 React 要求每个从数据动态创建的虚构DOM都有一个 key,所以用数组的 index 作为 key 是再适合不过了。所以列表这里我写了这样的代码:

list.map((item, index) => {  return (    <div key={index} /* 用index作为key */>      <Checkbox        checked={item.checked}        onClick={(e) => { /* handle click action */ }}      />      <Typography.Text code>{item.text}</Typography.Text>    </div>  );});

那么这里遇到了什么问题呢?就是新建一个行时,上面某一行的 CheckBox 执行了一个动画,这个动画是每次用户点击 CheckBox 而导致 CheckBox 的布尔值变动时应该执行的。语言有点难形容,我录了个屏来展现:

看到了没,每次点按钮,倒数第二条都执行一个动画。(图里其实还是不显著,咱们的产品中的动画成果更显著一点...)如果你切实get不到问题是什么样的,能够看看 antd 控件库的 CheckBox 组件的源代码... (开个玩笑,不过我真的看了一下 antd 代码佐证了一下)

大的背景

提醒: 介绍一下基本概念,晓得的话能够跳过。

要说分明这个问题为什么会产生,还是得把前端开发的一些背景说一下。我在学习前端开发的时候,曾经到了前端技术逐步趋于对立和稳固的时代,“数据驱动”和“虚构DOM技术”曾经取得了宽泛的认可和利用。简略解释一下这两个概念:

  • 数据驱动:意思是说,开发者致力于解决数据,前端库依据数据去渲染DOM节点,这种说法是绝对于开发者间接操作DOM节点和浏览器事件而言。在本文的例子中,咱们就是在关注数据(CheckBox的布尔值),CSS动画的渲染交给控件库去实现。
  • 基于虚构DOM的渲染:简略地说,基于虚构DOM的渲染是应用 JavaScript 对象示意DOM节点,通过比拟对象的差别来判断实在DOM节点的变动,这样做最终造成一个“缓冲”机制,尽量避免浏览器渲染流程中不必要的计算开销。在 React 和 Vue 中,算法依据虚构DOM的 key(在同类DOM节点中)辨认具体是哪个节点。

寻找起因

为了让虚构DOM节点能够被辨认,React 和 Vue 都要求开发者给动静生成DOM节点设置 key,否则会报这样的正告: Warning: Each child in a list should have a unique "key" prop. 为了便于实现,开发者首先会思考应用数组的 index 作为 key,然而数组的 index 肯定能精确地辨认惟一的虚构DOM节点吗?让咱们来用本文的例子比拟一下数组 index 和虚构DOM节点之间的关系。

在上图按按钮前后过程中,React的虚构DOM比拟算法认为产生了以下变动:

  • key为0的节点的 CheckBox 值没有发生变化。(算法断错了,这里理论是新增了 "electric blue" 这一行)
  • key为1的节点的 CheckBox 值由 false 变成了 true ,须要执行一个示意值变动的CSS动画(算法判断错了,"ocean of noise" 这行没有发生变化)
  • 新增了一个key为2的节点。(算法断错了,"some text" 这行没有发生变化)

这样看来,用 index 作为 key 让算法昏头昏脑,基本就没搞清谁是谁。这是在对数组进行增删是会产生的问题,如果数组没有变动(比方本文的例子中,产品自身没有减少记录的需要),其实不会呈现这个问题。除此之外,给数组排序也会导致这个问题,可想而知。(图片中的 Reverse 按钮就是我用来测试排序的)

问题的解决

解决这个问题很容易,应用一个能正确标识数组元素的字段作为虚构DOM的 key 就能够了。有时得益于业务上的便当,前端开发者常常能拿到一些后端 API 提供的唯一性 id,如果没有的话,本人创立一个随机数作为 id 也行。下图展现一下应用正确的 key 后的点击成果(没有那个一闪而过的动画了):

antd 控件中 List 的实现

我顺便看了一下 antd 的 List 控件是如何解决 key 问题的,从 源代码 能够看到,List 首先选用 rowKey,没有的话会尝试用数据源中的 key 字段,如果再没有,就用一个基于 index 的字符串。所以提供官网API反对的 rowKey 是最好的抉择。

援用的材料

  • 本文例子的源代码
  • 提到这个问题的 Vue 教程(我是看这个教程后才引起留神的,感激这个教程)
  • 本文也在我的博客公布了,这是 原文链接