From e696d800cca7936787d4c53c668e5c44f6e531bc Mon Sep 17 00:00:00 2001 From: mxrain Date: Wed, 7 Jan 2026 21:11:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B9=E8=BF=9B=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E8=B0=83=E8=AF=95=20Fiber=20=E9=81=8D=E5=8E=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BC=98=E5=85=88=E6=98=BE=E7=A4=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dev/ComponentDebugOverlay.tsx | 167 +++++++++++++------ vite.config.ts | 13 +- 2 files changed, 122 insertions(+), 58 deletions(-) diff --git a/src/components/dev/ComponentDebugOverlay.tsx b/src/components/dev/ComponentDebugOverlay.tsx index 56097bcbd..5cda153d5 100644 --- a/src/components/dev/ComponentDebugOverlay.tsx +++ b/src/components/dev/ComponentDebugOverlay.tsx @@ -20,72 +20,129 @@ function getReactFiberInfo(element: HTMLElement): Partial | null let fiber = (element as any)[fiberKey]; if (!fiber) return null; + // 收集组件链信息 + const componentChain: string[] = []; + let targetFiber = null; let depth = 0; + + // 遍历 Fiber 树找到最近的用户组件 while (fiber) { const type = fiber.type; if (type && typeof type === "function") { const name = type.displayName || type.name || "Anonymous"; - if (!name.startsWith("_") && name !== "Anonymous") { - // 尝试多种方式获取文件路径 - let filePath = ""; - - // 方式1: _debugSource (开发模式) - if (fiber._debugSource) { - const source = fiber._debugSource; - filePath = source.fileName || ""; - if (source.lineNumber) { - filePath += `:${source.lineNumber}`; - } + // 过滤掉内部组件和匿名组件 + if (!name.startsWith("_") && name !== "Anonymous" && !name.includes("Provider") && !name.includes("Consumer")) { + if (!targetFiber) { + targetFiber = fiber; } - // 方式2: type._source - else if (type._source) { - const source = type._source; - filePath = source.fileName || ""; - if (source.lineNumber) { - filePath += `:${source.lineNumber}`; - } - } - // 方式3: 尝试从函数的 toString 中提取(某些打包器会保留) - else if (type.toString) { - const funcStr = type.toString(); - // 检查是否有 sourceURL 注释 - const sourceMatch = funcStr.match(/\/\/# sourceURL=(.+)/); - if (sourceMatch) { - filePath = sourceMatch[1]; - } - } - - // 如果还是没有路径,显示提示信息 - if (!filePath) { - filePath = "仅开发模式可用 (需要 React DevTools)"; - } - - const props = fiber.memoizedProps || {}; - const safeProps: Record = {}; - for (const key of Object.keys(props)) { - const value = props[key]; - if (typeof value === "function") { - safeProps[key] = "[Function]"; - } else if (typeof value === "object" && value !== null) { - if (Array.isArray(value)) { - safeProps[key] = `[Array(${value.length})]`; - } else if (value.$$typeof) { - safeProps[key] = "[ReactElement]"; - } else { - safeProps[key] = "[Object]"; - } - } else { - safeProps[key] = value; - } - } - - return { name, filePath, props: safeProps, depth }; + componentChain.push(name); + if (componentChain.length >= 3) break; // 只收集前3个 } } fiber = fiber.return; depth++; } - return null; + + if (!targetFiber) { + // 如果没找到用户组件,使用第一个函数组件 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fiber = (element as any)[fiberKey]; + depth = 0; + while (fiber) { + const type = fiber.type; + if (type && typeof type === "function") { + const name = type.displayName || type.name; + if (name && name !== "Anonymous") { + targetFiber = fiber; + break; + } + } + fiber = fiber.return; + depth++; + } + } + + if (!targetFiber) return null; + + const type = targetFiber.type; + const name = type.displayName || type.name || "Unknown"; + + // 尝试多种方式获取文件路径 + let filePath = ""; + + // 方式1: _debugSource (开发模式 - React 17+) + if (targetFiber._debugSource) { + const source = targetFiber._debugSource; + filePath = source.fileName || ""; + if (source.lineNumber) { + filePath += `:${source.lineNumber}`; + if (source.columnNumber) { + filePath += `:${source.columnNumber}`; + } + } + } + // 方式2: __source prop (某些配置下) + else if (targetFiber.memoizedProps?.__source) { + const source = targetFiber.memoizedProps.__source; + filePath = source.fileName || ""; + if (source.lineNumber) { + filePath += `:${source.lineNumber}`; + } + } + // 方式3: _debugOwner 的 _debugSource + else if (targetFiber._debugOwner?._debugSource) { + const source = targetFiber._debugOwner._debugSource; + filePath = source.fileName || ""; + if (source.lineNumber) { + filePath += `:${source.lineNumber}`; + } + } + + // 简化路径显示 + if (filePath) { + // 移除绝对路径前缀,只保留 src/ 开始的部分 + const srcIndex = filePath.indexOf("/src/"); + if (srcIndex !== -1) { + filePath = filePath.substring(srcIndex + 1); + } + // Windows 路径 + const srcIndexWin = filePath.indexOf("\\src\\"); + if (srcIndexWin !== -1) { + filePath = filePath.substring(srcIndexWin + 1).replace(/\\/g, "/"); + } + } + + if (!filePath) { + filePath = "生产构建中不可用"; + } + + // 获取 props + const props = targetFiber.memoizedProps || {}; + const safeProps: Record = {}; + for (const key of Object.keys(props)) { + if (key.startsWith("_") || key === "__source" || key === "__self") continue; + const value = props[key]; + if (typeof value === "function") { + safeProps[key] = "[Function]"; + } else if (typeof value === "object" && value !== null) { + if (Array.isArray(value)) { + safeProps[key] = `[Array(${value.length})]`; + } else if (value.$$typeof) { + safeProps[key] = "[ReactElement]"; + } else { + safeProps[key] = "[Object]"; + } + } else { + safeProps[key] = value; + } + } + + return { + name, + filePath, + props: safeProps, + depth, + }; } /** 组件信息弹窗 */ diff --git a/vite.config.ts b/vite.config.ts index b9b54df1e..b8472d8f1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,8 +4,15 @@ import react from "@vitejs/plugin-react"; import svgr from "vite-plugin-svgr"; import path from "path"; -export default defineConfig({ - plugins: [react(), svgr()], +export default defineConfig(({ mode }) => ({ + plugins: [ + react({ + // 开发模式下启用 jsxDev 以获取组件源码位置 + jsxRuntime: mode === "development" ? "automatic" : "automatic", + jsxImportSource: "react", + }), + svgr(), + ], resolve: { alias: { "@": path.resolve(__dirname, "./src"), @@ -29,4 +36,4 @@ export default defineConfig({ "**/src-tauri/**", ], }, -}); +}));