鸿蒙 ArkTS 新增与编辑表单如何共用校验规则:partial、默认值与补丁合并
新增资料时,姓名和手机号都需要填写;编辑资料时,用户可能只修改姓名。
如果始终按完整表单校验,编辑请求会因为没有手机号而失败。若为编辑页面重新写一份规则,字段长度、格式和错误提示又容易出现差异。
这篇使用 @hmkit/validator 1.4.0,演示如何复用字段规则,并处理局部更新中的默认值、空值和数据合并。
ohpm install @hmkit/validator@1.4.0
1. 先定义新增时的完整规则
假设资料包含姓名、手机号和城市:
import { v } from '@hmkit/validator';
const createSchema = v.object({
'name': v.string().required('请输入姓名').min(2),
'phone': v.string().required('请输入手机号').phone(),
'city': v.string().required().default('未填写')
}).strict();
const createInput: Record<string, Object> = {
'name': '张三',
'phone': '13800138000'
};
const created = createSchema.parse(createInput);
if (created.success) {
console.info(JSON.stringify(created.value));
}
解析后的数据为:
{
"name": "张三",
"phone": "13800138000",
"city": "未填写"
}
这里使用 parse(),是为了取得默认值处理后的数据。业务后续应读取解析结果,而不是继续使用原始输入。
示例还使用了 .strict():如果输入出现没有定义的字段,会返回校验错误。对象默认会保留未知字段,需要根据接口契约选择处理方式。
2. 用 partial 派生局部更新规则
编辑接口只提交发生变化的字段,可以使用:
const updateSchema = createSchema.partial();
const updateInput: Record<string, Object> = {
'name': '李四'
};
const updated = updateSchema.parse(updateInput);
if (updated.success) {
console.info(JSON.stringify(updated.value));
}
输出为:
{
"name": "李四"
}
没有提交的 phone 和 city 会被跳过。name 一旦出现,仍需要满足原有的必填和最小长度要求。
partial() 返回新的对象 Schema,不会把 createSchema 的姓名和手机号也变成可选。但是,两者仍可能共享内部子规则,派生完成后应按约定管理这些可变实例。
3. 缺少字段和主动清空是两件事
对上面这个姓名规则,局部更新时的行为如下:
| 输入情况 | 结果 | 含义 |
|---|---|---|
| 没有 name 字段 | 通过 | 本次没有修改姓名 |
| name 为 undefined | 跳过 | partial 的缺失判断同样适用 |
| name 为空字符串 | 失败 | 字段出现后仍受 required 约束 |
| name 为 null | 失败 | 当前子规则不允许 null |
| name 为“李四” | 通过 | 执行姓名的原有规则 |
因此,partial 不会让所有空值都通过。
如果业务允许用户清空某个字段,需要单独约定清空方式,例如是否允许 null,并据此定义规则。不能把“没有传字段”和“明确要求清空字段”混为一谈。
编辑页面也应根据实际变化构造补丁。如果把未修改的输入框全部作为空字符串提交,就会触发这些字段的规则。
4. 为什么更新补丁不应该补齐默认值
新增时,city 缺失会得到默认值“未填写”。但编辑已有资料时,当前城市可能已经是“上海”。
如果修改姓名的补丁还自动补上 city 默认值,合并时就可能覆盖原有城市。
在上面的 partial 规则中,没有出现的字段会被跳过,因此这次姓名补丁只包含 name。默认值不会自动进入缺失的 city 字段。
这仍然只是补丁解析。要取得完整资料,需要与当前数据合并,再按完整规则检查。
function applyProfilePatch(
current: Record<string, Object>,
input: Record<string, Object>
): Record<string, Object> {
const patchResult = updateSchema.parse(input);
if (!patchResult.success || patchResult.value === undefined) {
throw new Error('局部更新字段校验失败');
}
const patch = patchResult.value;
if (Object.keys(patch).length === 0) {
throw new Error('没有需要更新的字段');
}
const merged: Record<string, Object> = {};
for (const key of Object.keys(current)) {
merged[key] = current[key];
}
for (const key of Object.keys(patch)) {
merged[key] = patch[key];
}
const finalResult = createSchema.parse(merged);
if (!finalResult.success || finalResult.value === undefined) {
throw new Error('合并后的完整资料校验失败');
}
return finalResult.value;
}
const currentProfile: Record<string, Object> = {
'name': '张三',
'phone': '13800138000',
'city': '上海'
};
const changes: Record<string, Object> = {
'name': '李四'
};
const result = applyProfilePatch(currentProfile, changes);
console.info(JSON.stringify(result));
结果中的姓名变成“李四”,城市仍然是“上海”。
这里的 currentProfile 只包含 Schema 定义的业务字段。如果实际记录还含有 id、创建时间等字段,需要先映射到相应的数据结构,再执行严格校验。
这段代码演示客户端的数据准备流程;最终写入和完整状态仍应由服务端按自己的业务规则验证。
5. 只允许更新一部分字段
如果某个编辑入口只开放姓名和城市,可以先挑选字段,再派生局部规则:
const basicInfoPatch = createSchema
.pick(['name', 'city'])
.partial();
const unexpected: Record<string, Object> = {
'phone': '13800138000'
};
console.info(`${basicInfoPatch.validate(unexpected).valid}`); // false
示例继承了 strict 策略,phone 不在这个派生 Schema 内,会被作为未知字段拒绝。
字段选择可以表达这个入口的数据范围,服务端仍需独立处理更新权限。
6. 嵌套对象需要单独考虑
普通 partial 只放宽当前对象层的字段。嵌套对象一旦出现,内部仍按原有规则检查:
const profileSchema = v.object({
'name': v.string().required(),
'address': v.object({
'city': v.string().required(),
'street': v.string().required()
})
});
const addressChanges: Record<string, Object> = {
'city': '上海'
};
const nestedChanges: Record<string, Object> = {
'address': addressChanges
};
console.info(`${profileSchema.partial().validate(nestedChanges).valid}`);
// false:address 已出现,street 仍然必填
console.info(`${profileSchema.deepPartial().validate(nestedChanges).valid}`);
// true:此处内置对象结构的字段被递归放宽
deepPartial 解决的是支持结构中的字段可选性。嵌套数据怎样合并,仍需要按业务契约定义。
前面的 applyProfilePatch 使用浅层合并,适用于示例里的平面字段。如果直接拿它处理 address,传入的 address 会替换旧对象,不能据此实现递归合并。
7. 跨字段规则和类型声明也要一起检查
partial 会保留对象级 refine。如果完整对象原本存在“结束时间晚于开始时间”等规则,单独提交一个字段时,回调可能拿不到另一半数据。
这类业务可以从同一份字段构造逻辑创建两种 Schema:补丁 Schema 检查允许提交的字段,完整 Schema 在合并后执行跨字段规则。不要假设 partial 会自动重写这些业务条件。
另外,v.object<Model>() 使用显式结果类型。调用 partial 不会自动把这个 Model 类型改造成可选字段类型,局部更新结果也不能直接当成完整模型。
需要类型化的更新接口时,应明确声明补丁模型,或按仓库支持的生成子集使用模型/Schema 单一来源工具。
项目地址与相关文档
如果你也在处理新增、编辑共用规则,可以先检查三件事:哪些字段允许缺失,哪些字段允许主动清空,以及补丁合并后是否仍满足完整业务规则。
更多推荐


所有评论(0)