React归并排序可视化useState不显示中间步骤及状态更新报错求解
问题诱因
- 闭包导致状态快照过时:初始调用
mergeSort时传入的mainArray是当时的状态快照,后续所有递归调用中拿到的mainArray都停留在初始值,并非最新的实时状态。基于旧值拼接的中间数组内容错误,甚至出现元素缺失、结构异常,最终导致展示区域空白。 - 缺失子数组位置标记:当前
mergeSort仅处理子数组的内容,没有记录这段子数组在原数组中的起止位置,拼接新数组时默认把合并结果放在最前面,覆盖了其他已经排好序的片段,中间态数组结构完全不符合预期,自然无法正确展示。你看到的“没有触发渲染”只是假象,实际上渲染的是错误的中间数组内容,直到最后一次全数组合并完成,才会显示正确的最终排序结果。 - 异步任务未做清理:归并排序的异步任务链很长,如果你在排序过程中重新触发排序、或者切换页面导致组件卸载,旧的异步任务仍然会继续执行,调用已卸载组件的
setArr就会触发你看到的警告,也可能进一步导致状态更新混乱。 - 关于所谓的
useState懒更新:你的理解存在偏差,useState的批量更新仅在同步执行场景下生效,你代码中所有await之后的setArr都会触发独立渲染,不需要用JSON.parse(JSON.stringify())做冗余深拷贝,展开运算符已经足够生成新的引用触发渲染。
解决方案
1. 调整mergeSort参数,新增位置标记
给mergeSort增加当前处理子数组在原数组中的起始索引start、结束索引end,合并完成后可以精准替换原数组对应位置的内容,不会覆盖其他已排序片段。
2. 使用函数式更新获取最新状态
setArr改用函数式写法,参数默认是当前最新的数组状态,彻底解决闭包拿到旧状态的问题,不需要再传入mainArray参数。
3. 增加异步任务中断逻辑
用useRef存储运行标记,组件卸载或者重新排序时中断旧的异步任务,避免更新已卸载组件的状态。
修改后的代码示例
组件内调用逻辑
const [arr, setArr] = useState(/* 你的初始数组 */) const isRunningRef = useRef(false) const DELAY = 50 // 你自己定义的动画间隔 // 触发排序的函数 const handleStartSort = async () => { // 每次排序前终止上一次的任务 isRunningRef.current = false // 等待一小段时间让旧任务全部终止 await delay(DELAY + 10) isRunningRef.current = true // 初始调用传入整个数组的起止索引 0 到 arr.length-1 await mergeSort([...arr], 0, arr.length - 1, setArr, isRunningRef) } // 清理函数,组件卸载时终止任务 useEffect(() => { return () => { isRunningRef.current = false } }, [])
调整后的mergeSort函数
async function mergeSort(arr, start, end, setArr, isRunningRef) { let n = arr.length; if (n === 1) { return arr; } const mid = Math.floor(n / 2); let leftArray = arr.slice(0, mid); let rightArray = arr.slice(mid, n); leftArray = await mergeSort(leftArray, start, start + mid - 1, setArr, isRunningRef); rightArray = await mergeSort(rightArray, start + mid, end, setArr, isRunningRef); let mergeResultArray = await merge(leftArray, rightArray, isRunningRef); // 如果已经终止运行就直接返回,不更新状态 if (!isRunningRef.current) return mergeResultArray; // 函数式更新,拿到最新的数组状态,精准替换对应区间的内容 setArr(prevArr => { const newArr = [...prevArr]; // 把合并结果替换到原数组对应的[start, end]区间 for (let i = 0; i < mergeResultArray.length; i++) { newArr[start + i] = mergeResultArray[i]; } return newArr; }); return mergeResultArray; }
调整后的merge函数
async function merge(array1, array2, isRunningRef) { let array3 = []; while (array1.length && array2.length) { await delay(DELAY); // 终止时直接返回剩余内容 if (!isRunningRef.current) return array3.concat(array1, array2); 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); if (!isRunningRef.current) return array3.concat(array1); array3.push(array1[0]); array1.splice(0, 1); } while (array2.length) { await delay(DELAY); if (!isRunningRef.current) return array3.concat(array2); array3.push(array2[0]); array2.splice(0, 1); } return array3; }
原有delay函数无需修改
const delay = (DELAY) => { return new Promise((resolve) => { setTimeout(() => { resolve(""); }, DELAY); }); };
内容的提问来源于stack exchange,提问作者SPARTACUS5329
相关产品推荐
相关产品推荐

