Реагировать на несогласованность перехода CSS посредством обновлений

В приведенном ниже фрагменте есть четыре поля. Цель состоит в том, что порядок этих ящиков будет перемешан, и при переходе в новое место будет происходить анимация перехода. Ключ каждого блока соответствует значению цвета из исходного массива в useState. При каждом обновлении с помощью кнопки перемешивания значения исходного массива перемешиваются. Затем я отображаю массив в возвращаемой функции. Я установил 2 classNames для каждого поля. Одно имя класса соответствует индексу и предназначено для позиционирования. Другое имя класса соответствует значению исходного массива и всегда согласовано с ключом для этого поля.

Моя проблема в том, что реакция, кажется, случайным образом решает, на какие ключи обращать внимание и согласовывать, а какие ключи игнорировать и просто перемонтировать эти элементы. Здесь вы можете видеть, что некоторые элементы правильно переходят, в то время как другие просто прыгают в свое целевое местоположение. Я не понимаю, почему это происходит. Может кто поможет?

РЕДАКТИРОВАТЬ: Я не считаю, что это проблема примирения в отношении нежелательного повторного монтажа. 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
);
.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;
}
<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>

1 ответ

Решение

Мне потребовалось время, чтобы понять это, потому что установка правильных ключей для ящиков казалась правильным делом.

Я проверил с помощью инструментов разработчика, что keys работы, проверив коробку и сохранив ее в переменной (b = $0), тасуя, заново осматривая коробку тем же key (цвет) и сравнивая его с сохраненным узлом ($0 === b, был true). Таким образом, узлы DOM для каждого ключа стабильны.

Но для переходов CSS этого недостаточно, потому что браузеры меняют порядок элементов в DOM.

Вы можете увидеть это здесь в минимизированном примере для эффективного переупорядочивания элементов в DOM (я предполагаю, что React делает аналогичные вещи внутри, когда элементы должны быть переупорядочены):

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

Запустите пример и установите точку останова DOM на полученном <ul> Узел 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
);
.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;
}
<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>

Примечание: теперь он работает, даже если нет key установлен.

Другие вопросы по тегам