如何让Valibot正确校验构造函数参数?为何校验未生效?
Valibot未校验构造函数参数的问题分析与解决
问题重现
Perl(Moops实现,校验正常)
use strictures; use Moops; use Data::Dx; library MyTypes declares NonNegativeInt { # 假设Types::Common::Numeric的PositiveOrZeroInt不存在 declare NonNegativeInt, as Int, where { $_ >= 0 }; }; class Span types MyTypes :ro { has from => isa => NonNegativeInt, required => true; has to => isa => NonNegativeInt, required => true; fun BUILD(InstanceOf["Span"] $self, HashRef $args) { die qq("from" $args->{from} must be smaller than "to" $args->{to}) if $args->{from} > $args->{to}; } } Dx Span->new(from => -123, to => 234); # 按预期抛出错误 Dx Span->new(from => 123, to => 234); # 正常运行 Dx Span->new(from => 1123, to => 234); # 按预期抛出错误 # 运行命令 # cpanm -nv -L local strictures Moops Data::Dx # perl -I local/lib/perl5 index.pl
JavaScript/TypeScript(Valibot实现,校验失效)
import * as v from "valibot"; const NonNegativeBigInt = v.pipe(v.bigint(), v.toMinValue(0n)); type NonNegativeBigInt = v.InferOutput<typeof NonNegativeBigInt>; class Span { readonly #from: NonNegativeBigInt; readonly #to: NonNegativeBigInt; constructor(readonly from: NonNegativeBigInt, readonly to: NonNegativeBigInt) { if (from > to) { throw new RangeError(`"from" ${from} must be smaller than "to" ${to}`); } this.#from = from; this.#to = to; } toString(): String { return `Span(${this.#from}, ${this.#to})`; } } console.log(new Span(-123n, 234n)); // 未抛出错误,不符合预期 console.log(new Span( 123n, 234n)); // 正常运行 console.log(new Span(1123n, 234n)); // 按预期抛出错误 # 运行命令 # npm i -D tsx # npm i -S valibot # npx tsx index.ts
问题原因
- TypeScript类型擦除:
NonNegativeBigInt是TypeScript静态类型,运行时会被完全擦除,JS引擎不会识别该约束,传入负BigInt时无任何校验触发。 - Valibot规则未执行:仅通过
v.pipe定义了校验规则,但未在构造函数中主动调用Valibot的校验方法(如v.parse)。Valibot是运行时校验工具,必须显式执行规则才会生效。
解决方案
要让Valibot生效,需在构造函数中主动执行校验逻辑,以下是两种常见实现方式:
方式1:直接校验单个参数
在构造函数中用v.parse校验每个参数,不符合规则时直接抛出错误:
import * as v from "valibot"; const NonNegativeBigInt = v.pipe(v.bigint(), v.toMinValue(0n)); type NonNegativeBigInt = v.InferOutput<typeof NonNegativeBigInt>; class Span { readonly #from: NonNegativeBigInt; readonly #to: NonNegativeBigInt; constructor(from: bigint, to: bigint) { // 显式调用Valibot校验参数 const validatedFrom = v.parse(NonNegativeBigInt, from); const validatedTo = v.parse(NonNegativeBigInt, to); if (validatedFrom > validatedTo) { throw new RangeError(`"from" ${validatedFrom} must be smaller than "to" ${validatedTo}`); } this.#from = validatedFrom; this.#to = validatedTo; // 可选:绑定校验后的值到公开属性 Object.assign(this, { from: validatedFrom, to: validatedTo }); } toString(): string { return `Span(${this.#from}, ${this.#to})`; } } console.log(new Span(-123n, 234n)); // 抛出Valibot校验错误 console.log(new Span(123n, 234n)); // 正常运行 console.log(new Span(1123n, 234n)); // 抛出RangeError
方式2:定义完整参数Schema
如果参数是对象形式,可定义完整Schema校验整个参数对象,适配复杂场景:
import * as v from "valibot"; const SpanSchema = v.object({ from: v.pipe(v.bigint(), v.toMinValue(0n)), to: v.pipe(v.bigint(), v.toMinValue(0n)), }); type SpanParams = v.InferInput<typeof SpanSchema>; class Span { readonly #from: v.InferOutput<typeof SpanSchema>["from"]; readonly #to: v.InferOutput<typeof SpanSchema>["to"]; constructor(params: SpanParams) { // 校验整个参数对象 const validatedParams = v.parse(SpanSchema, params); if (validatedParams.from > validatedParams.to) { throw new RangeError(`"from" ${validatedParams.from} must be smaller than "to" ${validatedParams.to}`); } this.#from = validatedParams.from; this.#to = validatedParams.to; } toString(): string { return `Span(${this.#from}, ${this.#to})`; } } console.log(new Span({ from: -123n, to: 234n })); // 抛出Valibot校验错误 console.log(new Span({ from: 123n, to: 234n })); // 正常运行 console.log(new Span({ from: 1123n, to: 234n })); // 抛出RangeError
补充说明
- 若需更友好的错误处理,可用
v.safeParse替代v.parse,它返回包含成功/失败状态的对象,而非直接抛出错误。 - TypeScript静态类型可与Valibot运行时校验配合,既在编译阶段提示错误,又在运行阶段保障数据合法性。
内容的提问来源于stack exchange,提问作者daxim
相关产品推荐
相关产品推荐

