React useState 初始化器的 TDZ 陷阱
症状
打开某页(v0.9.5 时是 SamTextPanel),整页白屏 + console:
ReferenceError: Cannot access 'projectQ' before initialization复现
tsx
function SamTextPanel() {
const [draft, setDraft] = useState(() => {
// ⚠️ 在 projectQ 声明前就读它
return loadDraft(projectQ.data?.id);
});
const projectQ = useProjectQuery(); // 这一行还没跑到
// ...
}useState 的初始化器在组件首次渲染时同步执行,此时下方 const projectQ = ... 还没走到,命中 TDZ(temporal dead zone)。
根因
JavaScript 的 let/const 在声明前访问会抛 ReferenceError(不像 var 拿到 undefined)。React useState(initializer) 的 initializer 在函数体执行到这一行时立即调用——但函数体内部声明的变量,要按词法顺序「执行到」才进入作用域。
容易踩的根本原因是:lazy initializer 看起来像「等需要时才跑」,开发者潜意识把它当成回调延迟执行。
修复
把被引用的声明前置,或改用 useEffect 在 mount 后初始化:
tsx
// ✅ 方案 A:先声明依赖
const projectQ = useProjectQuery();
const [draft, setDraft] = useState(() => loadDraft(projectQ.data?.id));
// ✅ 方案 B:mount 后再载入
const [draft, setDraft] = useState<Draft | null>(null);
useEffect(() => {
setDraft(loadDraft(projectQ.data?.id));
}, [projectQ.data?.id]);教训
useState的 lazy initializer 不是回调,是同步求值。- ESLint 的
no-use-before-define默认对函数声明放行、对变量声明严格——但 hook 依赖的语义它检不出来,需要人工 review。 - 排查白屏先看 console 第一条 ReferenceError,TDZ 通常告诉你确切变量名。
相关
- commit:
8949455fix(v0.9.5): SamTextPanel TDZ - 代码:
apps/web/src/pages/Workbench/SamTextPanel.tsx