为什么React的DOM协调不能按预期工作?

时间:2016-07-19 23:52:54

标签: reactjs

我正在尝试使用React交换元素的两个子元素。

<div style={{position: 'relative'}}>
  {this.state.items.map((item, index) => (
    <div
        key={item}
        style={{position: 'absolute',
                transform: `translateY(${index * 20}px)`,
                transition: '1s linear transform'}}>
      {item}
    </div>
  ))}
</div>

state.items是一个包含两个项目的数组。重新排序时,两个孩子div应该转换到新的位置。

现实中发生的事情是,当第二个元素按预期转换时,第一个元素会立即跳转。

据我所知,React认为它可以重用其中一个子元素,但不能重用另一个子元素,尽管文档说如果我们使用key属性,它应该总是重用元素:{ {3}}(至少,这就是我理解它的方式)。

我应该在代码中更改什么才能使其按预期工作?或者它是React中的错误吗?

实例:https://facebook.github.io/react/docs/reconciliation.html

1 个答案:

答案 0 :(得分:2)

警告:我在这个答案中做了一些假设,然而它会照亮你的一些(以及我之前的)问题。此外,我的解决方案几乎肯定会被简化,但为了回答这个问题,它应该是足够的。

这是一个很好的问题。打开开发工具并查看交换项目时实际发生的情况,我感到有些惊讶。

如果你看一下,你可以排序看看React是什么。第二个元素根本没有改变它的样式支柱,只是交换内部文本节点,而第一个元素作为新元素被放入dom中。

如果我不得不猜测,这是因为在数组中交换两个项目的方式是有效的,其中至少有一个项目被复制到临时变量并放回到数组中。

我想也许如果你使翻译随机,这两个元素都会得到新的道具和动画,但这只会让它更清晰,这不是预期的行为。

寻找解决方案

作为一个实验,如果我们提前创建节点,并通过React.cloneElement传递索引prop,该怎么办?当我们处于此状态时,让我们呈现span if index === 0div。无需担心的钥匙。

http://codepen.io/alex-wilmer/pen/pbaXzQ?editors=1010

开放开发工具现在可以准确地说明React的意图。 React保留元素并仅更改相关部分,在本例中为innerText节点和元素类型。因为样式完全以1:1交换,所以不需要更新样式。

<强>解决方案:

你可以提前生成你的React元素,将它们保存在一个数组中,因此没有任何键可以随意移动并找出如何放回到DOM中。然后使用不同的数组来跟踪预期的顺序。可能非常复杂,但它确实有效!

http://codepen.io/alex-wilmer/pen/kXZKoN?editors=1010

const Item = function (props) {
  return (
    <div
      style={{position: 'absolute',
        transform: `translateY(${props.index * 20}px)`,
        transition: '0.5s linear transform'}}>
      {props.children}
    </div>
  )
}

const App = React.createClass({
  getInitialState () {
    return {
      items: [
        {item: 'One', C: <Item>One</Item>}, 
        {item: 'Two', C: <Item>Two</Item>}
      ],
      order: ['One', 'Two']
    };
  },
  swap () {
    this.setState({
      order: [this.state.order[1], this.state.order[0]]
    });
  },
  render: function () {
    return <div>
      <button onClick={this.swap}>Swap</button>
      <div style={{position: 'relative'}}>
       {this.state.items.map(x => 
          React.cloneElement(x.C, { 
            index: this.state.order.findIndex(z => z === x.item) 
          }))
       }
      </div>
    </div>;
  }
});