删除一项会导致 React 删除最后一个 DOM 节点,而不是与该项目关联的节点

Removing an item causes React to remove the last DOM node instead of the one associated with that item

我试图用 ReactCSSTransitionGroup 为列表的插入和移除设置动画,但移除动画总是只为列表的最后一项设置动画,而不是正在删除的那个。

Here's a jsbin to illustrate this problem。尝试按 "Add" 按钮以验证插入动画确实按预期工作,然后单击任何项​​目旁边的 "x" 以查看列表的最后一项动画而不是您的动画的问题试图删除。

是我在设置 TransitionGroup 时做错了什么,还是我在 CSS 转换定义中遗漏了什么?

您遇到此问题是因为您使用 index 作为密钥:

let nodes = items.map((item, index) => {
  let idx = index
  return (<Item key={index} value={item} index={index} _delete={this._onDelete}/>)
})

React 在虚拟 DOM 差异期间使用 key 属性 来确定删除了哪个元素,但索引永远无法充分满足此目的。

考虑这个例子:你从下面的数组开始,结果是下面的 DOM 结构:

const arr = [2, 4, 6, 8];

<li key={0}>2</li>
<li key={1}>4</li>
<li key={2}>6</li>
<li key={3}>8</li>

然后假设您删除了索引 2 处的元素。您现在拥有以下数组和以下 DOM 结构:

const arr = [2, 4, 8];

<li key={0}>2</li>
<li key={1}>4</li>
<li key={2}>8</li>

请注意 8 现在位于索引 2 中; React 发现这个 DOM 结构与上一个结构的区别在于缺少带有键 3li,因此将其删除。因此,无论您删除了哪个数组元素,生成的 DOM 结构都将缺少带有键 3.

li

解决方案是为列表中的每个项目使用唯一标识符;在实际应用程序中,您可能需要使用 id 字段或其他一些主键;对于像这样的应用程序,您可以生成一个递增的 ID:

let id = 0;
class List extends Component {
  constructor() {
    this.state = {
      items: [{id: ++id, value: 1}, {id: ++id, value: 2}]
    }

    // ...
  }

  _onClick(e) {
    this.state.items.push({id: ++id, value: Math.round(Math.random() * 10)})
    this.setState({items: this.state.items})
  }

  // ...

  render() {
    let items = this.state.items
    let nodes = items.map((item, index) => {
      let idx = index
      return (<Item key={item.id} value={item.value} index={index} _delete={this._onDelete}/>)
    })

    // ...
  }
}

工作示例:http://jsbin.com/higofuhuni/2/edit