通过更新反应 CSS 转换不一致
React CSS Transition Inconsistency Through Updates
下面的代码片段有四个框。目的是这些盒子的顺序将被打乱,并且当它们到达新位置时会出现过渡动画。每个框的键对应于 useState 中源数组中的一个颜色值。每次通过随机播放按钮进行更新时,源数组的值都会被随机播放。然后我在 return 函数中映射数组。我为每个框设置了 2 个类名。一个类名对应索引,用于定位。另一个类名与源数组值相对应,并且始终与该框的键一致。
我的问题是 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
);
.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
的工作原理,方法是检查一个盒子并将其存储在一个变量 (b = [=16=]
) 中,洗牌,用相同的 [= 重新检查盒子=15=](颜色)并将其与存储的节点([=18=] === 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>
运行 示例并在生成的 <ul>
DOM 节点上为 "Subtree modification" 设置一个 DOM 断点,参见屏幕截图。
如果您单击 "reorder!",浏览器首先在删除 <li>
时中断。如果您继续,并且在继续(Firefox:<F8>
)之后,浏览器会立即再次中断并插入 <li>
.
(在我的测试中,Chrome 提供的有关中断的信息有点误导,Firefox 在这方面做得更好)
因此浏览器在技术上将重新排序实现为 "remove and insert",这打破了 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
,它现在也能正常工作。
下面的代码片段有四个框。目的是这些盒子的顺序将被打乱,并且当它们到达新位置时会出现过渡动画。每个框的键对应于 useState 中源数组中的一个颜色值。每次通过随机播放按钮进行更新时,源数组的值都会被随机播放。然后我在 return 函数中映射数组。我为每个框设置了 2 个类名。一个类名对应索引,用于定位。另一个类名与源数组值相对应,并且始终与该框的键一致。
我的问题是 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
);
.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
的工作原理,方法是检查一个盒子并将其存储在一个变量 (b = [=16=]
) 中,洗牌,用相同的 [= 重新检查盒子=15=](颜色)并将其与存储的节点([=18=] === 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>
运行 示例并在生成的 <ul>
DOM 节点上为 "Subtree modification" 设置一个 DOM 断点,参见屏幕截图。
如果您单击 "reorder!",浏览器首先在删除 <li>
时中断。如果您继续,并且在继续(Firefox:<F8>
)之后,浏览器会立即再次中断并插入 <li>
.
(在我的测试中,Chrome 提供的有关中断的信息有点误导,Firefox 在这方面做得更好)
因此浏览器在技术上将重新排序实现为 "remove and insert",这打破了 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
,它现在也能正常工作。