Zod递归判别联合Schema引发ReferenceError: Cannot access '__vite_ssr_import_1__' before initialization的解决方案咨询
Zod递归判别联合Schema引发ReferenceError: Cannot access 'vite_ssr_import_1' before initialization的解决方案咨询
这个问题的根源是循环引用的变量初始化时序冲突,结合Zod处理递归Schema的特性,我们可以通过z.lazy()来完美解决,下面是具体的分析和修改方案:
问题原因拆解
你遇到的错误本质是JavaScript的变量提升规则导致的:
- 当调用
createNodeSchema('leadingFoo')创建leadingFooNodeSchema时,代码会执行createNodeSchema里的childrengetter逻辑,此时childBarNodeSchema还未定义(它在leadingFooNodeSchema之后声明); - 直接在Schema中引用未初始化的变量,JS引擎会抛出
Cannot access 'X' before initialization的错误。
Zod专门提供了z.lazy() API处理递归/循环引用场景,它可以延迟Schema的解析时机,直到实际需要校验时才执行引用,完美避开初始化时序问题。
具体修改方案
1. 重构基础Schema生成函数
移除createNodeSchema中硬编码的子节点Schema,改用z.lazy()引用一个全局的节点联合Schema:
import { z } from 'zod'; // 先声明空变量,用于后续递归引用 let allNodeSchemas: z.ZodDiscriminatedUnion<'type', any>; function createNodeSchema<const NodeType extends string>(nodeType: NodeType) { return z.object({ id: z.string(), type: z.literal(nodeType), // 用z.lazy()延迟解析,避开初始化时序问题 children: z.array(z.lazy(() => allNodeSchemas)), }); }
2. 定义各节点专属Schema
这一步和原有逻辑一致,但现在createNodeSchema的children不会直接引用未定义变量:
// 根节点类型Schema const leadingFooNodeSchema = createNodeSchema('leadingFoo').extend({ foo: z.string(), }); // 子节点类型Schema const childBarNodeSchema = createNodeSchema('followingBar').extend({ bar: z.string(), });
3. 初始化全局节点联合Schema
在所有节点Schema定义完成后赋值allNodeSchemas,此时所有变量已初始化,无引用风险:
// 所有节点Schema已定义,可安全组合成判别联合Schema allNodeSchemas = z.discriminatedUnion('type', [ leadingFooNodeSchema, childBarNodeSchema, // 可添加其他节点类型... ]);
4. 调整根节点Schema(可选)
如果根节点只能是特定类型(如仅leadingFoo),可单独组合根节点的判别联合:
const rootNodeBaseSchema = z.discriminatedUnion('type', [leadingFooNodeSchema]); const rootNodeSchema = rootNodeBaseSchema.refine(haveNodesUniqueIDs, { error: 'Nodes must have unique IDs', });
5. 优化唯一ID校验函数(可选)
你的RecursivePick类型定义是正确的,这里补充一个更健壮的校验实现:
type RecursivePick<T, K extends keyof T> = { [P in Extract<keyof T, K>]: P extends 'children' ? T[P] extends Array<infer U> ? RecursivePick<U, Extract<keyof U, K>>[] : never : T[P]; }; type NodeSchemaWithIDAndChildren = RecursivePick<z.infer<typeof rootNodeSchema>, 'id' | 'children'>; function haveNodesUniqueIDs(node: NodeSchemaWithIDAndChildren): boolean { const seenIds = new Set<string>(); const traverse = (current: NodeSchemaWithIDAndChildren): boolean => { if (seenIds.has(current.id)) return false; seenIds.add(current.id); return current.children.every(traverse); }; return traverse(node); }
方案有效性说明
z.lazy(() => allNodeSchemas)将Schema引用包装为函数,Zod仅在实际校验时才调用该函数;- 校验执行时,
allNodeSchemas已被赋值为完整的判别联合Schema,所有节点Schema均已初始化,不会再出现引用错误; - 完全保留了判别联合的校验能力,每个节点的类型和字段都能被正确校验。
内容来源于stack exchange
相关产品推荐
相关产品推荐

