useState未显示中间步骤



我正在制作一个排序可视化器,我已经完成了气泡、插入和选择。我遇到了合并排序的问题,因为它涉及递归,并且在更新状态变量时似乎存在一些问题。

async function mergeSort(arr, setArr, mainArray) {
let n = arr.length;
if (n === 1) {
return arr;
}
let leftArray = arr.slice(0, Math.floor(n / 2));
let rightArray = arr.slice(Math.floor(n / 2), n);
leftArray = await mergeSort(leftArray, setArr, mainArray);
rightArray = await mergeSort(rightArray, setArr, mainArray);
let mergeResultArray = await merge(leftArray, rightArray);
let l = mergeResultArray.length;
let tempArr = [...mergeResultArray];
let copyArr = [...mainArray];
let subArr = copyArr.slice(l, mainArray.length);
tempArr.push(...subArr);
setArr(JSON.parse(JSON.stringify(tempArr)));
return mergeResultArray;
}
async function merge(array1, array2) {
let array3 = [];
while (array1.length && array2.length) {
await delay(DELAY);
if (array1[0].val > array2[0].val) {
array3.push(array2[0]);
array2.splice(0, 1);
} else {
array3.push(array1[0]);
array1.splice(0, 1);
}
}
while (array1.length) {
await delay(DELAY);
array3.push(array1[0]);
array1.splice(0, 1);
}
while (array2.length) {
await delay(DELAY);
array3.push(array2[0]);
array2.splice(0, 1);
}
return array3;
}
const delay = (DELAY) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("");
}, DELAY);
});
};

setArruseState返回的函数,用于更新传递给函数的主组件上的数组。await merge()之后的部分只是在合并每个步骤之后的部分以可视化中间步骤之后创建当前mainArray

现在发生的情况是,在调用函数后,主组件在数组应该所在的位置变为空白,一段时间后,它出现了,并且完全排序。中间setArr()没有显示在组件上。

在了解到setArr(arr)需要大量时间(懒惰((因为它在其他排序算法中也被延迟(后,我立即使用JSON.parse(JSON.stringify())来渲染它。

可能是什么问题?我如何解决这个问题以显示所有中间步骤?

此外,我得到这个错误

index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. at Bar (http://localhost:3000/static/js/main.chunk.js:281:13) at li

如果它有助于

11月12日编辑。

我创建了一个沙盒。做了很多修改,甚至添加了typescript(我不喜欢没有类型支持的编程(。希望这会有所帮助。


据我所知,在React Components(也是钩子(中进行复杂计算的最佳方法是never do that in React Components。在React中编写代码有严格的规则,如重新渲染的ticker、钩子的规则等。将复杂的计算与React混合会导致意外行为,并且很难调试。相反,将其移出React,在组件安装后订阅结果,并在卸载前取消订阅。这就是我对避免React中的错误的全部理解,即让React只做视图。

为什么useState似乎跳过步骤

待定。(当我确定后,我会更新这个答案(

useState的错误

如果在组件卸载后调用setState,则会引发该错误。在Github上的代码中,您可以触发排序,这是一个异步进程,但如果在进程中途卸载组件,则没有任何东西可以取消该进程。这就是为什么会出现这种错误。组件不再渲染,您仍在尝试更新其状态。

改为使用生成器函数

一个更好的方法是使用Generator函数而不是Async函数。生成器函数与异步函数几乎完全相同,只是使用了工作yield而不是等待。这样,函数就去了;暂停";每次收益,您可以从外部手动恢复。

这也会让你的可视化效果更好,因为你不需要静态的DELAY,而且你可以让用户一次点击一个步骤。

最棒的是,您的代码看起来基本相同。如果您仍然想自动推进可视化,您可以在React组件本身中放置一个setInterval,这将在卸载时更容易清理。

以下是Generator功能如何工作的基本示例:

function* mergeSort(arr, setArr, mainArray) {
let n = arr.length;
if (n === 1) {
return arr;
}
let leftArray = arr.slice(0, Math.floor(n / 2));
let rightArray = arr.slice(Math.floor(n / 2), n);
leftArray = yield* mergeSort(leftArray, setArr, mainArray);
rightArray = yield* mergeSort(rightArray, setArr, mainArray);
let mergeResultArray = yield* merge(leftArray, rightArray);
let l = mergeResultArray.length;
let tempArr = [...mergeResultArray];
let copyArr = [...mainArray];
let subArr = copyArr.slice(l, mainArray.length);
tempArr.push(...subArr);
// Cheaper way to copy.
setArr(tempArr.slice());
return mergeResultArray;
}
function* merge(array1, array2) {
let array3 = [];
while (array1.length && array2.length) {
yield;
if (array1[0].val > array2[0].val) {
array3.push(array2[0]);
array2.splice(0, 1);
} else {
array3.push(array1[0]);
array1.splice(0, 1);
}
}
while (array1.length) {
yield;
array3.push(array1[0]);
array1.splice(0, 1);
}
while (array2.length) {
yield;
array3.push(array2[0]);
array2.splice(0, 1);
}
return array3;
}
// Usage within React:
let [array, setArray] = useState([]);
let iterRef = useRef();
const generateNewArray = () => {
setArray(generateRandomArr(5, 650, ARRAY_SIZE));
};
const mergeSortStep = () => {
// This will do one step on every click.
iterRef.current.next();
};
useEffect(() => {
setArray(generateRandomArr(5, 650, ARRAY_SIZE));
iterRef.current = mergeSort([...array], setArray, [...array]);
}, []);

@naman-goel已经为您提供了一个很好的答案,但我想补充一点,您不必在进行排序时进行可视化,您也可以进行排序并存储步骤,然后单独进行可视化。这样,您还可以存储可视化的状态,而无需再次进行排序。如果你真的想多做一点,你也可以用画布来绘制可视化效果,而不是在每一步都重新绘制一个组件,这样它看起来就不会持续闪烁。

React还会每10ms或其他时间批量更新一次,所以如果你的状态更新在10ms内发生了10次,它也不会显示你的所有可视化,它只会跳到下一个~10步。

最新更新