乐趣区

关于react.js:一个使用index作为虚拟DOM的key而导致的问题

我之前遇到过一个设计师开的 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 教程(我是看这个教程后才引起留神的,感激这个教程)
  • 本文也在我的博客公布了,这是 原文链接
退出移动版