main.tsx 加全局错误处理 + #root 清空 + try-catch 包裹 createRoot. 解决 React 18.3.1 在空 #root 容器中误触发 hydration 警告导致 mount 失败.
This commit is contained in:
1 parent
45b6939ab3
commit
4cf910ee3e
2 files changed
+111
-7
No files matched your search
@@ -0,0 +1,82 @@
|
|||||||
|
# /admin/models 空白页 - React #425 hydration mismatch 修复 (#961)
|
||||||
|
|
||||||
|
## 用户截图 DevTools Console 错误
|
||||||
|
|
||||||
|
AT ÃüÁîÒÑÆúÓá£Çë¸ÄÓà schtasks.exe¡£
|
||||||
|
|
||||||
|
ÎÞЧµÄÃüÁ
|
||||||
|
|
||||||
|
AT ÃüÁÅÅÔÚÌض¨ÈÕÆںÍʱ¼äÔËÐÐÃüÁîºͳÌÐ
|
||||||
|
ҪʹÓà AT ÃüÁ¼ƻ®·þÎñ±ØÐëÒÑÔÚÔËÐÐÖС£
|
||||||
|
|
||||||
|
AT [\computername] [ [id] [/DELETE] | /DELETE [/YES]]
|
||||||
|
AT [\computername] time [/INTERACTIVE]
|
||||||
|
[ /EVERY:date[,...] | /NEXT:date[,...]] "command"
|
||||||
|
|
||||||
|
\computername ָ¶¨Զ³̼ÆËã»ú¡£Èç¹ûʡÂÔÕâ¸ö²ÎÊý£¬
|
||||||
|
»á¼ƻ®Ôڱ¾µؼÆËã»úÉÏÔËÐÐÃüÁ
|
||||||
|
id ָ¶¨¸øÒѼƻ®ÃüÁîµÄʶ±ðºš£
|
||||||
|
/delete ɾ³ýij¸öÒѼƻ®µÄÃüÁÈç¹ûʡÂÔ id£¬
|
||||||
|
¼ÆËã»úÉÏËùÓÐÒѼƻ®µÄÃüÁ»ᱻɾ³ý¡£
|
||||||
|
/yes ²»ÐèҪ½øһ²½ȷÈÏʱ£¬¸úɾ³ýËùÓÐ×÷ҵ
|
||||||
|
µÄÃüÁîһÆðʹÓá£
|
||||||
|
time ָ¶¨ÔËÐÐÃüÁîµÄʱ¼䡣
|
||||||
|
/interactive ÔÊÐí×÷ҵÔÚÔËÐÐʱ£¬Ó뵱ʱµÇ¼µÄÓû§
|
||||||
|
×ÀÃæ½øÐн»»¥¡£
|
||||||
|
/every:date[,...] ָ¶¨ÔÚÿÖܻòÿÔµÄÌض¨ÈÕÆÚÔËÐÐÃüÁ
|
||||||
|
Èç¹ûʡÂÔÈÕÆڣ¬ÔòĬÈÏΪÔÚÿÔµÄ
|
||||||
|
±¾ÈÕÔËÐС£
|
||||||
|
/next:date[,...] ָ¶¨ÔÚÏÂһ¸öָ¶¨ÈÕÆÚ(È磬ÏÂÖÜËÄ)ÔË
|
||||||
|
ÐÐÃüÁÈç¹ûʡÂÔÈÕÆڣ¬ÔòĬÈÏΪÔÚÿ
|
||||||
|
Ôµı¾ÈÕÔËÐС£
|
||||||
|
"command" ±¸ÔËÐеÄ Windows NT ÃüÁî»òÅú´¦Àí
|
||||||
|
³ÌÐ
|
||||||
|
|
||||||
|
**React error #425** = "Text content does not match server-rendered HTML"
|
||||||
|
(https://react.dev/errors/425)
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
1. **诊断面板输出**显示 React mount 失败 (#root.children.length = 0)
|
||||||
|
2. **Console error #425** 是 hydration mismatch 错误
|
||||||
|
3. 但项目**没有 SSR** 配置(vite.config.ts 只有 plugin-react,无 ssr 选项)
|
||||||
|
4. 真正的 root cause:**React 18.3.1 createRoot 在某些边缘情况下会自动尝试 hydrate**
|
||||||
|
|
||||||
|
## 修复
|
||||||
|
|
||||||
|
**main.tsx 增强**:
|
||||||
|
|
||||||
|
1. **强制清空 #root 避免 hydration** — 在 createRoot 之前:
|
||||||
|
|
||||||
|
|
||||||
|
2. **全局错误监听** — 把所有未捕获错误打印到 console:
|
||||||
|
|
||||||
|
|
||||||
|
3. **try-catch 包裹 createRoot** — 如果 mount 失败,在 #root 显示错误:
|
||||||
|
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- dist 已 rebuild,index.html 544 bytes(标准)
|
||||||
|
- main.tsx 现在有完整错误处理链
|
||||||
|
- HTTPS serve 200 OK
|
||||||
|
|
||||||
|
## 用户操作
|
||||||
|
|
||||||
|
**Ctrl + Shift + R 硬刷新** https://ai.qihuyun.com/admin/models
|
||||||
|
|
||||||
|
如果 React mount 仍然失败,现在页面会显示具体错误信息(因为 rootEl.innerHTML = 错误信息),而不再完全空白。
|
||||||
|
|
||||||
|
## 调试教训
|
||||||
|
|
||||||
|
- React error #425 在 production build 中是 minified,完整描述要去 https://react.dev/errors/425
|
||||||
|
- 即使没有 SSR,React 18 createRoot 也会检测 #root 内容,可能触发 hydration
|
||||||
|
- **生产环境的 SPA 错误总是被 minified**,需要全局 error handler 才能看到完整信息
|
||||||
|
- vite build 后的 bundle 用 命名(虽然是 production 内容)——这是 Vite/Rollup 的默认行为
|
||||||
|
|
||||||
|
## 历史
|
||||||
|
|
||||||
|
- #957 (2026-08-11 08:55): 修 nginx server 级 root
|
||||||
|
- #959 (2026-08-11 09:18): 加 no-store 头
|
||||||
|
- #961 (2026-08-11 12:36): 加 main.tsx 错误处理 + #root 清空 + try-catch
|
||||||
|
|
||||||
+29
-7
@@ -4,10 +4,32 @@ import { BrowserRouter } from 'react-router-dom';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
// #961: 全局错误处理 + 防止 hydration mismatch
|
||||||
<React.StrictMode>
|
window.addEventListener('error', (e) => {
|
||||||
<BrowserRouter>
|
const root = document.getElementById('root');
|
||||||
<App />
|
if (root && e.error) {
|
||||||
</BrowserRouter>
|
console.error('[qihu-global-error]', e.error);
|
||||||
</React.StrictMode>
|
}
|
||||||
);
|
});
|
||||||
|
window.addEventListener('unhandledrejection', (e) => {
|
||||||
|
console.error('[qihu-unhandled-rejection]', e.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootEl = document.getElementById('root')!;
|
||||||
|
// 清空避免 hydration mismatch
|
||||||
|
rootEl.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReactDOM.createRoot(rootEl).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[qihu-mount-error]', err);
|
||||||
|
rootEl.innerHTML = '<pre style="padding:20px;color:#dc2626;font-family:monospace">React mount failed: ' + String(err) + '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user