通过更新反应 CSS 过渡不一致

Jor*_*rdy 5 javascript key reactjs react-hooks reconcile

下面的代码段有四个框。目的是这些框的顺序将被打乱,并在它们转到新位置时发生过渡动画。每个框的键对应于 useState 中源数组的颜色值。每次通过 shuffle 按钮更新时,源数组的值都会被打乱。然后我在返回函数中映射数组。我为每个框设置了 2 个 classNames。一个类名与索引对应,用于定位。另一个类名对应于源数组值,并且始终与该框的键一致。

我的问题是,react 似乎随机决定要注意和协调哪些键,以及忽略哪些键并重新安装这些元素。您可以在这里看到,一些元素正确过渡,而其他元素只是跳转到它们的目标位置。我不知道为什么会发生这种情况。有人可以帮忙吗?

编辑:我不认为这是不需要重新安装的协调问题。React 正确地尊重了密钥,而不是重新安装任何密钥。所以问题在于 React 如何处理更新期间添加的 CSS 转换类。有些过渡有效,有些则无效。这可能只是引擎的限制,但如果有人有任何进一步的煽动,请分享。

const {useState} = React;

function App() {
  const [state, setState] = useState(['Red', 'Green', 'Blue', 'Black'])

  function handleShuffle() {
    const newState = _.shuffle(state)
    setState(newState)
  }

  return ( 
    <div className="App"> 
      {state.map((sourceValue, index) => {
        return ( 
          <div className={
            'box positionAt' + index + ' sourceValue' + sourceValue
          }
          key={sourceValue} ></div>
        )
      })}

      <button id="shuffle" onClick={handleShuffle}> shuffle < /button> 
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
Run Code Online (Sandbox Code Playgroud)
.App {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: gray;
}

.box {
  width: 25px;
  height: 25px;
  position: absolute;
  transition: transform 1s;
}

.positionAt0 {
  transform: translate(0px, 0px);
}

.positionAt1 {
  transform: translate(175px, 0px);
}

.positionAt2 {
  transform: translate(0px, 175px);
}

.positionAt3 {
  transform: translate(175px, 175px);
}

.sourceValueGreen {
  background-color: green;
}

.sourceValueBlue {
  background-color: blue;
}

.sourceValueRed {
  background-color: red;
}

.sourceValueBlack {
  background-color: black;
}

#shuffle {
  position: absolute;
  top: 0px;
  left: 75px;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)

Seb*_* B. 2

我花了一段时间才弄清楚,因为为盒子设置正确的钥匙似乎是正确的事情。

我使用开发人员工具验证了keys 的工作原理,方法是检查一个框并将其存储在变量 ( b = $0) 中,进行洗牌,重新检查具有相同key(颜色)的框并将其与存储的节点 ( $0 === b, was true) 进行比较。所以每个键的 DOM 节点都是稳定的。

但这对于 CSS 转换来说还不够,因为浏览器改变 DOM 中元素顺序的方式。

您可以在一个最小化的示例中看到它,用于有效地重新排序 DOM 中的元素(我假设React 在必须重新排序元素时在内部执行类似的操作):

function reorder() {
    const list = document.querySelector("ul");
    list.appendChild(list.firstElementChild);
}
Run Code Online (Sandbox Code Playgroud)
 <ul>
    <li>List-item #1</li>
    <li>List-item #2</li>
</ul>
<button onclick="reorder()">reorder!</button>
Run Code Online (Sandbox Code Playgroud)

运行示例并在生成的<ul>DOM 节点上设置一个 DOM 断点以进行“子树修改”,请参见屏幕截图。

在此输入图像描述

如果您单击“重新排序!”,浏览器将首先在删除<li>. 如果您继续,并且在继续后立即 (Firefox:) <F8>,浏览器会再次中断,并插入一个<li>.

(在我的测试中,Chrome 提供的有关中断的信息有点误导,Firefox 在这方面做得更好)

因此,浏览器在技术上将重新排序实现为“删除和插入”,这会破坏 CSS 转换。

有了这些知识,就可以通过在 DOM 中固定框的顺序来轻松修复代码(不需要更改 DOM 中的顺序,因为位置仅通过类设置):

(注:*HTML 和 CSS 不变,JavaScript 中的更改标有 NEW 或 CHANGE *)

const {useState} = React;

// NEW: List of boxes for a stable order when rendering to the DOM:
const boxes = ['Red', 'Green', 'Blue', 'Black'];

function App() {
  const [state, setState] = useState(boxes); // CHANGE: reuse boxes here

  function handleShuffle() {
    const newState = _.shuffle(state)
    setState(newState)
  }

  return ( 
    <div className="App"> 
      {/* CHANGE: loop over boxes, not state and lookup position, which is used for the positionAt... class */ 
       boxes.map((sourceValue, index) => {
         const position = state.indexOf(sourceValue);
         return ( 
           <div className={
             'box positionAt' + position + ' sourceValue' + sourceValue
           }
           key={sourceValue} ></div>
         )
      })}

      <button id="shuffle" onClick={handleShuffle}> shuffle < /button> 
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
Run Code Online (Sandbox Code Playgroud)
.App {
  position: relative;
  width: 200px;
  height: 200px;
  background-color: gray;
}

.box {
  width: 25px;
  height: 25px;
  position: absolute;
  transition: transform 1s;
}

.positionAt0 {
  transform: translate(0px, 0px);
}

.positionAt1 {
  transform: translate(175px, 0px);
}

.positionAt2 {
  transform: translate(0px, 175px);
}

.positionAt3 {
  transform: translate(175px, 175px);
}

.sourceValueGreen {
  background-color: green;
}

.sourceValueBlue {
  background-color: blue;
}

.sourceValueRed {
  background-color: red;
}

.sourceValueBlack {
  background-color: black;
}

#shuffle {
  position: absolute;
  top: 0px;
  left: 75px;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<div id="root"></div>
Run Code Online (Sandbox Code Playgroud)

注意:即使没有key设置,它现在也可以工作。