Реагировать на несогласованность перехода 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 ответ
Мне потребовалось время, чтобы понять это, потому что установка правильных ключей для ящиков казалась правильным делом.
Я проверил с помощью инструментов разработчика, что key
s работы, проверив коробку и сохранив ее в переменной (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
установлен.