React如何在切换至gameboard前完成图片加载与数据转换?
问题原因及解决方案
问题根源
你的代码核心问题在于图片加载是异步操作:convertAllLayouts函数里的img.onload回调要等图片加载完成后才会执行,但函数本身会立刻返回空的convertedLayouts数组。之后useEffect直接用这个空数组更新layoutShapes,并马上切换到gameboard页面,等图片实际加载完成并转换数据时,页面已经渲染完毕,自然无法拿到正确的布局数据。
解决方案
把图片加载和转换逻辑改成异步Promise形式,等待所有图片处理完成后再更新状态、切换页面。具体修改如下:
1. 重构convertAllLayouts为异步函数
将每个图片的加载和转换封装成Promise,用Promise.all等待所有任务完成后返回结果:
async function convertAllLayouts(ref, presets) { if (presets.length === 0) return []; // 把每个图片的处理逻辑封装成Promise const conversionPromises = presets.map(presetUrl => { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'Anonymous'; const ctx = ref.current.getContext('2d', { alpha: true, desynchronized: false, colorSpace: 'srgb', willReadFrequently: true }); ctx.globalCompositeOperation = "copy"; img.onload = function () { ctx.imageSmoothingEnabled = false; ctx.drawImage(img, 0, 0); const imageData = ctx.getImageData(0, 0, 16, 16, {colorSpace: 'srgb'}).data; const newConvertedPreset = []; for (let rowIndex = 0; rowIndex < 16; rowIndex++) { const newRow = []; // 计算当前行的像素起始位置 const rowStart = rowIndex * 16 * 4; for (let i = rowStart; i < rowStart + 16 * 4; i += 4) { // 根据alpha通道判断像素值 newRow.push(imageData[i + 3] === 255 ? 1 : 0); } newConvertedPreset.push(newRow); } resolve(newConvertedPreset); }; // 处理图片加载失败的情况 img.onerror = function () { reject(new Error(`Failed to load image: ${presetUrl}`)); }; img.src = presetUrl; }); }); // 等待所有图片转换完成 return Promise.all(conversionPromises); }
2. 修改useEffect使用async/await等待结果
在useEffect里定义异步处理函数,等待convertAllLayouts完成后再更新状态并切换页面:
useEffect(() => { // 定义异步处理函数 const handleLayoutConversion = async () => { if (!isCustomLayoutShapesSubmitClicked) return; try { const layouts = await convertAllLayouts(canvasRef, presetLayouts); if (layouts.length === 0) { // 生成默认布局(注意:需确保tileDimensions变量已提前定义) let defaultLayoutShape = []; for (let rowIndex = 0; rowIndex < tileDimensions.h; rowIndex++) { const newRow = new Array(tileDimensions.w).fill(1); if ((tileDimensions.h * tileDimensions.w) % 2 !== 0) { if (rowIndex === Math.floor(tileDimensions.h / 2)) { newRow[Math.floor(tileDimensions.w / 2)] = 0; } } defaultLayoutShape.push(newRow); } setLayoutShapes([defaultLayoutShape, 'default']); } else { setLayoutShapes([...layouts]); } // 所有处理完成后再切换页面 setDisplay('gameboard'); } catch (error) { console.error('Layout conversion failed:', error); // 可在此添加错误提示或回退逻辑 } finally { setIsCustomLayoutShapesSubmitClicked(false); } }; handleLayoutConversion(); }, [isCustomLayoutShapesSubmitClicked])
3. 额外注意点
- 确保
tileDimensions变量在组件内已定义,原代码中未看到该变量的声明,需补充。 - 建议添加加载状态(比如按钮置灰、显示加载动画),避免用户重复点击触发多次转换。
内容的提问来源于stack exchange,提问作者Felt_Notebook
相关产品推荐
相关产品推荐

