AntD 日期控件已挂上 5 处 ← 首屏 AntD 日期控件已挂上 1 处 ← 点开任务浮层的编辑 react-dom.min.js:121 Error: Minified React error #321 ← Invalid hook call at Object.V (VM320 react-dom.min.js:105:148) at c.useContext (VM303 react.min.js:28:406) at e (antd-with-locales.min.js:77:5889) ... renderPicker @ app.js:2279 mountAntdSingleDT @ app.js:2282 bootAntd @ app.js:1957 window.fbBootAntd @ app.js:2501 loadEdit @ entry_popup.js:280
官方三种成因:① React 版本不一致 ② 存在多份 React 副本 ③ 在组件外调 hook。
这里的是 ②。
VM
VM320 react-dom.min.js VM303 react.min.js
VM<数字> 是浏览器给"通过 eval / new Function 动态执行的代码"分配的脚本 ID。 普通的 <script src> 加载显示的是文件 URL(如 react-dom.min.js?v=6735aef8)。
VM<数字>
<script src>
react-dom.min.js?v=6735aef8
同一个文件出现 VM 前缀 ⇒ 它被动态执行了不止一次。
loadJs
app.js 里有两个独立的 IIFE,各自定义了一个 loadJs:
app.js
Semi 段(约 1600 行):
function loadJs(src) { return new Promise(function (res, rej) { var s = document.createElement('script'); s.src = src; s.onload = ... }); }
boot() 里无条件调用:
boot()
loadJs(conf.react) .then(function () { return loadJs(conf.dom); }) .then(function () { return loadJs(conf.ui); })
antd 段(约 93 行):
if (!window.React) { chain = chain.then(function () { return loadJs(conf.react || ''); }); }
看着更谨慎,但依赖"当前全局有没有 React" —— 而 UMD 要执行完才赋值。
① 首页加载,Semi 的 boot() 用 requestIdleCallback 排到"空闲时" ② 用户点开任务 → 浮层 → 编辑 → fbBootAntd() ③ antd 的 ensure():!window.React ⇒ 开始加载 react.min.js ← 第一次 ④ 与此同时/稍后,Semi 的 idle 回调触发 → boot() → 无条件 loadJs(react.min.js) ← 第二次 ⑤ 同一段 UMD 执行两次 ⇒ window.React 被覆盖成"新的那套" ⑥ 但 antd 内部模块早已捕获"第一次"的 React 引用 ⑦ antd 的 useContext 在"第二套"的 dispatcher 上调用 ⇒ #321
ensure() 有个致命位置问题:
ensure()
if (window.antd && window.dayjs && window.React && window.ReactDOM) { return Promise.resolve(); }
React 已就绪时会直接 return —— 这正是首屏 "已挂上 5 处" 之后不再加载的原因, 但也意味着首次加载时两边真的会各插一个 <script>。
<script>
src
两处 loadJs 都改成共享一个全局登记表:
function loadJs(src) { var reg = window.__fbJsLoaded; if (!reg) { reg = window.__fbJsLoaded = {}; } if (reg[src]) { return reg[src]; } // 已加载/加载中 ⇒ 复用同一个 Promise reg[src] = new Promise(function (res, rej) { ... }); return reg[src]; }
为什么用 window.__fbJsLoaded 而不是各 IIFE 内的变量: 两个 loadJs 在不同的 IIFE 闭包里,各自的局部变量互相看不见。
window.__fbJsLoaded
为什么不用 if (!window.React) 那种判断: UMD 执行完才赋值。两个请求几乎同时发出时,两边都还看不到 window.React, 照样会各插一个。按 URL 去重是确定的,不依赖执行时序。
if (!window.React)
window.React
=== antd 先,Semi 的 idle 后到 === antd -> 新插入 ← 真实执行 semi -> 复用 ✅ 不重复执行 结果: ✅ 只有一份 React === Semi 先,antd 后 === semi -> 新插入 | antd -> 复用 ✅ 结果: ✅ 只有一份 React === 不同文件应各自加载 === react.min.js -> 新插入 ✅ antd.min.js -> 新插入 ✅(没被错误复用)
AntD 日期控件已挂上 N 处
与 #72(任务浮层缺 ref_id 导致编辑按钮不显示)是两件事:#72 是按钮压根没出现;本单是按钮出现后、点编辑时 AntD 控件因 React 重复加载而崩溃。修完 #72 才暴露出来的下一个 bug。
No dependencies set.
The note is not visible to the blocked user.
React #321:Semi 与 antd 各自加载 React,执行两遍导致两套 React
用户(2026-11-05)Console 输出
React #321 = Invalid hook call
官方三种成因:① React 版本不一致 ② 存在多份 React 副本 ③ 在组件外调 hook。
这里的是 ②。
决定性证据:
VM前缀VM<数字>是浏览器给"通过 eval / new Function 动态执行的代码"分配的脚本 ID。普通的
<script src>加载显示的是文件 URL(如react-dom.min.js?v=6735aef8)。同一个文件出现
VM前缀 ⇒ 它被动态执行了不止一次。根因:两个
loadJs都不去重app.js里有两个独立的 IIFE,各自定义了一个loadJs:Semi 段(约 1600 行):
boot()里无条件调用:antd 段(约 93 行):
看着更谨慎,但依赖"当前全局有没有 React" —— 而 UMD 要执行完才赋值。
时序
ensure()有个致命位置问题:React 已就绪时会直接 return —— 这正是首屏 "已挂上 5 处" 之后不再加载的原因,
但也意味着首次加载时两边真的会各插一个
<script>。修法:按
src全局去重两处
loadJs都改成共享一个全局登记表:为什么用
window.__fbJsLoaded而不是各 IIFE 内的变量:两个
loadJs在不同的 IIFE 闭包里,各自的局部变量互相看不见。为什么不用
if (!window.React)那种判断:UMD 执行完才赋值。两个请求几乎同时发出时,两边都还看不到
window.React,照样会各插一个。按 URL 去重是确定的,不依赖执行时序。
验证(复刻逻辑跑)
验收
AntD 日期控件已挂上 N 处为什么单独建单
与 #72(任务浮层缺 ref_id 导致编辑按钮不显示)是两件事:#72 是按钮压根没出现;本单是按钮出现后、点编辑时 AntD 控件因 React 重复加载而崩溃。修完 #72 才暴露出来的下一个 bug。