为什么排序时元素不在正确的位置?
Why elements are not in the right place while sorting?
我正在学习 React 并决定尝试创建一个排序可视化工具。我从冒泡排序开始,几乎成功地创建了一个基本的可视化工具。我添加了更改排序速度和排序数组长度的选项。我测试了几次,发现有时在显示排序数组时,某些元素不在位。当你有一个高大的元素不在它应该在的地方时可以看到这一点(场景后面的数组本身被正确排序)。所以显示元素发生了一些事情,我不知道是什么。
排序完成后,如果某些元素没有到位,如果我突然改变速度,元素会跳回它们应该在的位置。我想这是因为 speed
是状态的一部分,并且正在重新渲染。
我应该怎么做才能解决这个问题?
这是我的代码:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'white';
const COMPARE_COLOR = 'darkred';
const DONE_COLOR = 'green';
const SPEED = 4;
const SPEEDS = [1, 5, 10, 25, 50, 100, 150, 200, 250, 300];
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy = () => {
const [arr, setArr] = useState([]);
const [numberOfElements, setNumberOfElements] = useState(NUMBER_OF_ELEMENTS);
const [speed, setSpeed] = useState(SPEED);
const timeout_id = [];
useEffect(() => {
generateArray();
}, [numberOfElements]);
const reset = () => {
resetColors();
generateArray();
}
const generateArray = () => {
const arr1 = [];
for(let i = 0; i < numberOfElements; i++)
{
arr1[i] = randomIntFromInterval(5, 100);
}
console.log(arr1);
setArr(arr1);
}
const resetColors = () => {
const arrayBars = document.getElementsByClassName('array-bar');
for(let i = 0; i < arrayBars.length; i++) {
arrayBars[i].style.backgroundColor = DEFAULT_COLOR;
}
}
const bubbleSort = (arr, n) => {
let i, j, temp, swapped, delay = 1;
for(i = 0; i < n - 1; i++)
{
swapped = false;
for(j = 0; j < n - i - 1; j++)
{
createColor([j, j + 1], COMPARE_COLOR, delay++);
if(arr[j] > arr[j + 1])
{
// swap arr[j] and arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
createAnimation(j, j + 1, delay++);
}
createColor([j, j + 1], DEFAULT_COLOR, delay++);
}
createColor([n - i - 1], DONE_COLOR, delay++);
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
const leftovers = [];
for(let k = 0; k < n - i - 1; k++) {
leftovers.push(k);
}
createColor(leftovers, DONE_COLOR, delay++);
}
const createAnimation = (one, two, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
const barOneHeight = arrayBars[one].style.height;
const barTwoHeight = arrayBars[two].style.height;
arrayBars[two].style.height = `${barOneHeight}`;
arrayBars[one].style.height = `${barTwoHeight}`;
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const createColor = (indexes, color, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
for(let i = 0; i < indexes.length; i++) {
arrayBars[indexes[i]].style.backgroundColor = color;
}
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const handleSort = (arr) => {
bubbleSort(arr, arr.length);
}
const handlerRange = (e) => {
setNumberOfElements(e.target.value);
}
const stopTimeOuts =() => {
for(let i = 0; i < timeout_id.length; i++) {
clearTimeout(timeout_id[i]);
}
}
const handleSpeed = (e) => {
setSpeed(e.target.value);
}
const maxVal = Math.max(...arr);
return (
<div>
<div className="array-container" style={{height: '50%', backgroundColor: 'black'}}>
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: DEFAULT_COLOR,
height: `${(value * 100 / maxVal).toFixed()}%`,
width: `${85 / arr.length}%`,
display: 'inline-block',
margin: '0 1px'
}}>
</div>
))}
</div>
<div className="buttons-container">
<button onClick={() => handleSort(arr)}>Sort!</button>
<button onClick={() => reset()}>Reset</button>
<button onClick={() => stopTimeOuts()}>Stop!</button>
</div>
number of elements: {numberOfElements}
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
speed: {speed}
<div className="slider-container">
1
<input type="range"
min="1"
max="10"
onChange={(e) => handleSpeed(e)}
className="slider"
id="myRange"
/>
10
</div>
</div>
);
}
export default Dummy;
将 div 中的键设置为唯一标识符而不是 idx
- 在这种情况下,您可以使用 value
:
/* -- snip -- */
<div className="array-bar"
key={value}
/* -- snip -- */
这将停止反应回收每个 div 及其各自的索引,而是根据新数组重新渲染和重新排序。
您看到意外行为的原因是 React 使用键来标识每个元素。 因此,如果您对数组进行排序,然后在渲染时应用索引作为 id,React 将获取首先使用该索引作为键渲染的元素并将其放在那个位置。 通过更改唯一标识符的关键,React 不会混淆元素(因为唯一标识符永远不会改变其元素)并且现在可以按照您想要的顺序准确呈现每个项目。
示例:
我们呈现一个列表:
<div id=1>Foo</div> // Id "1" is now bound to this element
<div id=2>Bar</div>
因此,如果您随后像这样重新排序列表(注意 ID 更改):
<div id=1>Bar</div> // this will be transformed to <div id=1>Foo</div>
<div id=2>Foo</div> // this will be transformed to <div id=2>Bar</div>
然后 React 会将元素转换为它们首先分配给的任何 ID - 这就是为什么拥有 唯一标识符 很重要的原因。您可以通过生成 uuid 或其他足够随机的字符串的库生成唯一标识符。
您可以在此处阅读更多内容:https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
我正在学习 React 并决定尝试创建一个排序可视化工具。我从冒泡排序开始,几乎成功地创建了一个基本的可视化工具。我添加了更改排序速度和排序数组长度的选项。我测试了几次,发现有时在显示排序数组时,某些元素不在位。当你有一个高大的元素不在它应该在的地方时可以看到这一点(场景后面的数组本身被正确排序)。所以显示元素发生了一些事情,我不知道是什么。
排序完成后,如果某些元素没有到位,如果我突然改变速度,元素会跳回它们应该在的位置。我想这是因为 speed
是状态的一部分,并且正在重新渲染。
我应该怎么做才能解决这个问题?
这是我的代码:
import React, { useContext, useState, useEffect } from 'react';
const NUMBER_OF_ELEMENTS = 10;
const DEFAULT_COLOR = 'white';
const COMPARE_COLOR = 'darkred';
const DONE_COLOR = 'green';
const SPEED = 4;
const SPEEDS = [1, 5, 10, 25, 50, 100, 150, 200, 250, 300];
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
const Dummy = () => {
const [arr, setArr] = useState([]);
const [numberOfElements, setNumberOfElements] = useState(NUMBER_OF_ELEMENTS);
const [speed, setSpeed] = useState(SPEED);
const timeout_id = [];
useEffect(() => {
generateArray();
}, [numberOfElements]);
const reset = () => {
resetColors();
generateArray();
}
const generateArray = () => {
const arr1 = [];
for(let i = 0; i < numberOfElements; i++)
{
arr1[i] = randomIntFromInterval(5, 100);
}
console.log(arr1);
setArr(arr1);
}
const resetColors = () => {
const arrayBars = document.getElementsByClassName('array-bar');
for(let i = 0; i < arrayBars.length; i++) {
arrayBars[i].style.backgroundColor = DEFAULT_COLOR;
}
}
const bubbleSort = (arr, n) => {
let i, j, temp, swapped, delay = 1;
for(i = 0; i < n - 1; i++)
{
swapped = false;
for(j = 0; j < n - i - 1; j++)
{
createColor([j, j + 1], COMPARE_COLOR, delay++);
if(arr[j] > arr[j + 1])
{
// swap arr[j] and arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
createAnimation(j, j + 1, delay++);
}
createColor([j, j + 1], DEFAULT_COLOR, delay++);
}
createColor([n - i - 1], DONE_COLOR, delay++);
// If no two elements were
// swapped by inner loop, then break
if(swapped === false) break;
}
const leftovers = [];
for(let k = 0; k < n - i - 1; k++) {
leftovers.push(k);
}
createColor(leftovers, DONE_COLOR, delay++);
}
const createAnimation = (one, two, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
const barOneHeight = arrayBars[one].style.height;
const barTwoHeight = arrayBars[two].style.height;
arrayBars[two].style.height = `${barOneHeight}`;
arrayBars[one].style.height = `${barTwoHeight}`;
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const createColor = (indexes, color, delay) => {
const arrayBars = document.getElementsByClassName('array-bar');
const id = setTimeout(() => {
for(let i = 0; i < indexes.length; i++) {
arrayBars[indexes[i]].style.backgroundColor = color;
}
}, SPEEDS[speed - 1] * delay);
timeout_id.push(id);
}
const handleSort = (arr) => {
bubbleSort(arr, arr.length);
}
const handlerRange = (e) => {
setNumberOfElements(e.target.value);
}
const stopTimeOuts =() => {
for(let i = 0; i < timeout_id.length; i++) {
clearTimeout(timeout_id[i]);
}
}
const handleSpeed = (e) => {
setSpeed(e.target.value);
}
const maxVal = Math.max(...arr);
return (
<div>
<div className="array-container" style={{height: '50%', backgroundColor: 'black'}}>
{arr.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{
backgroundColor: DEFAULT_COLOR,
height: `${(value * 100 / maxVal).toFixed()}%`,
width: `${85 / arr.length}%`,
display: 'inline-block',
margin: '0 1px'
}}>
</div>
))}
</div>
<div className="buttons-container">
<button onClick={() => handleSort(arr)}>Sort!</button>
<button onClick={() => reset()}>Reset</button>
<button onClick={() => stopTimeOuts()}>Stop!</button>
</div>
number of elements: {numberOfElements}
<div className="slider-container">
1
<input type="range"
min="1"
max="100"
onChange={(e) => handlerRange(e)}
className="slider"
id="myRange"
/>
100
</div>
speed: {speed}
<div className="slider-container">
1
<input type="range"
min="1"
max="10"
onChange={(e) => handleSpeed(e)}
className="slider"
id="myRange"
/>
10
</div>
</div>
);
}
export default Dummy;
将 div 中的键设置为唯一标识符而不是 idx
- 在这种情况下,您可以使用 value
:
/* -- snip -- */
<div className="array-bar"
key={value}
/* -- snip -- */
这将停止反应回收每个 div 及其各自的索引,而是根据新数组重新渲染和重新排序。
您看到意外行为的原因是 React 使用键来标识每个元素。 因此,如果您对数组进行排序,然后在渲染时应用索引作为 id,React 将获取首先使用该索引作为键渲染的元素并将其放在那个位置。 通过更改唯一标识符的关键,React 不会混淆元素(因为唯一标识符永远不会改变其元素)并且现在可以按照您想要的顺序准确呈现每个项目。
示例: 我们呈现一个列表:
<div id=1>Foo</div> // Id "1" is now bound to this element
<div id=2>Bar</div>
因此,如果您随后像这样重新排序列表(注意 ID 更改):
<div id=1>Bar</div> // this will be transformed to <div id=1>Foo</div>
<div id=2>Foo</div> // this will be transformed to <div id=2>Bar</div>
然后 React 会将元素转换为它们首先分配给的任何 ID - 这就是为什么拥有 唯一标识符 很重要的原因。您可以通过生成 uuid 或其他足够随机的字符串的库生成唯一标识符。
您可以在此处阅读更多内容:https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318