Реагировать на несогласованность перехода 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>


person Jordy    schedule 22.05.2020    source источник
comment
Просто для информации, похоже, что вещи переходят только тогда, когда их новое место находится на месте раньше их предыдущего. Если это пятно после, они не переходят. Кроме того, я не думаю, что это проблема с картой, потому что я пытался реализовать это без карты, но все еще имею ту же проблему.   -  person Jordy    schedule 22.05.2020
comment
У меня та же проблема, компонент, поднимающийся вверх, останется переходом, а компонент, идущий вниз, потеряет переход   -  person Quoc Van Tang    schedule 02.12.2020


Ответы (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.

person Sebastian B.    schedule 25.05.2020
comment
Если бы я мог как-то подарить вам репутацию, я бы сделал это. Это очень важно для того, что я пытаюсь сделать в отношении интерактивной галереи изображений. И я верю, что это будет прекрасно работать! Не могу дождаться, чтобы увидеть, как это работает. Еще раз спасибо. - person Jordy; 25.05.2020