Vant 4 ContactEdit 联系人编辑组件完全指南:表单校验、事件回调与主题定制
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
导读
ContactEdit 是 Vant 4 移动端组件库中负责"编辑并保存联系人信息"的表单型组件,通常与 ContactList 联系人列表、ContactCard 联系人卡片 组合,构成完整的"地址簿 / 收货人管理"流程。本指南以 ContactEdit 官方文档 为主体,结合 ContactEdit.tsx、index.less 等源码与 测试用例,系统讲解其 Props/Events 全量 API、内置表单校验逻辑、编辑与新增两种模式,以及基于 CSS 变量的主题定制方案。读完本文,你将能够独立完成一个带校验、带默认联系人开关、支持编辑与删除的联系人表单页面。
组件定位与应用场景
ContactEdit 面向的典型业务场景是:用户在下单流程中维护收货人/联系人信息,包括姓名、手机号,并可标记该联系人为"默认"。组件本身是一个自包含的表单(内部基于 Vant 的 Form 表单 与 Field 输入框 构建),在提交前自动完成姓名必填、手机号格式两类校验,并把校验通过后的表单内容通过save事件交还给业务方持久化。
在 Vant 内部,ContactEdit与ContactList、ContactCard、AddressEdit等组件共享同一套联系人类别数据模型,ContactEditInfo数据结构的字段名(name、tel、isDefault)与联系人列表组件保持对齐,方便在同一页面中双向传值。
快速引入与组件注册
Vant 4 全面基于 Vue 3 与 TypeScript 构建,使用前需确认项目已安装vant包并处于 Vue 3 环境。文档给出了全局注册方式:
import { createApp } from 'vue'; import { ContactEdit } from 'vant'; const app = createApp(); app.use(ContactEdit);除了全局注册,Vant 也支持按需引入。从源码看,组件在 index.ts 中通过withInstall包装后导出,并同时注册了全局组件名VanContactEdit:
import { withInstall } from '../utils'; import _ContactEdit from './ContactEdit'; export const ContactEdit = withInstall(_ContactEdit); export default ContactEdit; export { contactEditProps } from './ContactEdit'; export type { ContactEditInfo, ContactEditProps } from './ContactEdit'; export type { ContactEditThemeVars } from './types'; declare module 'vue' { export interface GlobalComponents { VanContactEdit: typeof ContactEdit; } }因此模板中可以直接使用<van-contact-edit>(kebab-case)或<VanContactEdit>,TypeScript 环境还能获得全局组件类型提示。若采用按需引入(如使用unplugin-vue-components),则无需app.use(),直接import { ContactEdit } from 'vant'后在局部注册即可,更多注册方式可参考 Vant 文档的组件注册章节。
代码演示:编辑联系人表单的完整写法
文档中的"基础用法"演示了编辑模式下最典型的使用形态。其对应实现可在仓库的 demo/index.vue 中看到,模板部分与文档示例一致:
<van-contact-edit is-edit show-set-default :contact-info="editingContact" set-default-label="设为默认联系人" @save="onSave" @delete="onDelete" />import { ref } from 'vue'; import { showToast } from 'vant'; export default { setup() { const editingContact = ref({ tel: '', name: '', }); const onSave = (contactInfo) => showToast('保存'); const onDelete = (contactInfo) => showToast('删除'); return { onSave, onDelete, editingContact, }; }, };这个示例的关键点在于:
is-edit控制是否展示删除按钮:源码 ContactEdit.tsx 中,删除按钮仅在props.isEdit为真时渲染,也就是说"新增联系人"场景下不应传入该属性;show-set-default+set-default-label控制默认联系人栏:仅当showSetDefault为真时渲染"设为默认联系人"的开关行,set-default-label用于自定义该行文案;contact-info为受控初值:以ref维护,编辑时传入已有联系人数据,新增时传入空对象{ tel: '', name: '' };@save/@delete回调:两个事件都会把当前表单内容对象作为回调参数传出,便于业务层执行保存接口或删除接口。
从 demo 源码可以看出,演示页面在保存/删除后只是showToast提示,真实项目中应在回调里调用接口并跳转或刷新列表页。
API 全量解析
Props 参数
以下参数来自 ContactEdit.tsx 的contactEditProps定义与官方文档,两者完全一致:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| contact-info | 联系人信息 | ContactEditInfo | {} |
| is-edit | 是否为编辑联系人 | boolean | false |
| is-saving | 是否显示保存按钮加载动画 | boolean | false |
| is-deleting | 是否显示删除按钮加载动画 | boolean | false |
| tel-validator | 手机号格式校验函数 | (tel: string) => boolean | isMobile |
| show-set-default | 是否显示默认联系人栏 | boolean | false |
| set-default-label | 默认联系人栏文案 | string | - |
结合源码补充两点容易被忽略的实现细节:
contact-info的默认值并非直接共享对象,而是default: () => extend({}, DEFAULT_CONTACT),每次实例化都会生成新的{ tel: '', name: '' },避免多个实例间意外共享引用。组件内部通过reactive维护一份表单副本,并通过watch(() => props.contactInfo, ...)在外部数据变化时同步,测试用例 index.spec.ts 专门验证了"setProps 更新 contactInfo 后提交表单,save 事件携带最新数据"这一行为;tel-validator的默认校验器是 Vant 工具函数isMobile,实现在 utils/basic.ts:它先剥离非数字/连字符字符,再匹配((+86)|(86))?1\d{10}(中国大陆 11 位手机号,可带 86/+86 前缀)或0[0-9-]{10,13}(带区号的座机号)。若业务需要其他手机号规则(如海外号码),传入自定义校验函数即可,例如:tel-validator="(tel) => /^1[3-9]\d{9}$/.test(tel)"。
Events 事件
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| save | 点击保存按钮时触发 | content:表单内容 |
| delete | 点击删除按钮时触发 | content:表单内容 |
| change-default | 切换是否为默认联系人时触发 | checked:是否默认 |
源码中事件声明为emits: ['save', 'delete', 'changeDefault'](ContactEdit.tsx),需要注意 Vue 3 中 kebab-case 的事件在模板里写作@change-default,组件内使用 camelCase 的changeDefault。
save事件的触发逻辑值得展开:保存按钮的nativeType="submit",整个组件外层包着一个<Form onSubmit={onSave}>,因此保存事件由表单提交驱动。而onSave内部有防重复提交判断——if (!props.isSaving) emit('save', contact)。这意味着业务侧收到save事件后,应把is-saving置为true(同时可配合保存按钮的loading动画),在接口返回后再置回false,从而既展示加载态又防止重复提交。同理is-deleting控制删除按钮的 loading 态。
change-default在用户拨动"设为默认联系人"的 Switch 时触发,回调参数为开关的布尔值。源码 ContactEdit.tsx 中该开关直接双向绑定contact.isDefault,业务侧通常用它同步到后端"默认联系人"标记。
ContactEditInfo 数据结构
ContactEditInfo是组件对外交换的联系人数据模型,类型定义位于 ContactEdit.tsx:
export type ContactEditInfo = { tel: string; name: string; isDefault?: boolean; };| 键名 | 说明 | 类型 |
|---|---|---|
| name | 联系人姓名 | string |
| tel | 联系人手机号 | string |
| isDefault | 是否默认 | boolean | undefined |
注意isDefault是可选字段:新增联系人时通常为undefined,只有用户主动打开开关后才变为true/false。save/delete事件回调参数content即为该结构的完整对象,可直接用于接口提交。
类型定义
组件对外导出以下类型,供业务侧在 TypeScript 中做类型收窄:
import type { ContactEditInfo, ContactEditProps } from 'vant';此外 index.ts 还导出了ContactEditThemeVars(用于主题变量类型)与contactEditProps(props 定义对象),其中ContactEditProps由ExtractPropTypes<typeof contactEditProps>推导而来,保证类型与运行时 props 定义始终一致。
内置表单校验机制
ContactEdit 的校验不依赖业务方手写,而是由组件内部两个 Field 的rules完成,见 ContactEdit.tsx:
- 姓名字段:
rules = [{ required: true, message: t('nameEmpty') }],即姓名必填,未填写时展示"请填写姓名"错误文案; - 手机号字段:
rules = [{ validator: props.telValidator, message: t('telInvalid') }],即通过tel-validator(默认isMobile)校验,失败时展示"请填写正确的电话"错误文案。
文案通过 Vant 国际化机制提供,默认中文在 locale/lang/zh-CN.ts 中定义(name: '姓名'、tel: '电话'、save: '保存'、delete: '删除'、nameEmpty: '请填写姓名'、telInvalid: '请填写正确的电话'),切换语言环境会自动跟随。姓名字段还设置了maxlength="30"限制输入长度。
测试用例 index.spec.ts 完整覆盖了这套校验链路:
- 姓名为空时提交表单,断言
.van-field__error-message渲染出错误提示(快照比对); - 电话为空时提交表单,同样断言错误提示渲染;
- 校验通过后提交,断言
save事件携带的contactInfo与传入值逐字段相等; - 更新
contact-infoprop 后再提交,断言事件携带最新数据; is-edit模式下点击删除按钮,会弹出删除确认对话框(van-dialog),点击确认后delete事件被触发。
其中删除操作弹出确认框这一行为由 demo 测试中.van-dialog__confirm的点击可见(index.spec.ts),说明组件在真实使用中删除属于"二次确认"的破坏性操作,业务回调里收到delete事件时数据已被确认过。
编辑模式与新增模式的区别
通过 Props 组合,ContactEdit 可以同时服务"新增"与"编辑"两种场景:
| 场景 | contact-info | is-edit | 删除按钮 |
|---|---|---|---|
| 新增联系人 | { tel: '', name: '' }或空对象 | false(不传) | 不渲染 |
| 编辑联系人 | 传入已有联系人对象 | true | 渲染,点击后触发delete |
从源码 ContactEdit.tsx 可以确认:删除按钮的渲染条件就是props.isEdit,因此新增模式下组件天然只有"保存"一个主按钮。编辑模式下将列表页选中的联系人对象传给contact-info,组件内部watch会同步副本,用户修改后提交即可。
主题定制:CSS 变量与 ConfigProvider
ContactEdit 的样式完全通过 CSS 变量驱动,支持两种定制方式:直接覆盖:root下的变量,或借助 ConfigProvider 组件 按作用域动态配置。
组件暴露的样式变量定义在 index.less 中,与官方文档表格一一对应:
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-contact-edit-padding | var(--van-padding-md) | 组件整体内边距 |
| --van-contact-edit-fields-radius | var(--van-radius-md) | 输入区域圆角 |
| --van-contact-edit-buttons-padding | var(--van-padding-xl) 0 | 按钮区域内边距 |
| --van-contact-edit-button-margin-bottom | var(--van-padding-sm) | 按钮下外边距 |
| --van-contact-edit-button-font-size | var(--van-font-size-lg) | 按钮字号 |
| --van-contact-edit-field-label-width | 4.1em | 输入框标签宽度 |
所有默认值都引用了 Vant 基础设计令牌(如--van-padding-md、--van-radius-md、--van-font-size-lg),因此定制时建议同样基于这些基础变量做相对调整,保持视觉体系一致。类型层面,types.ts 定义了ContactEditThemeVars,对应上述六个变量名的驼峰形式(如contactEditFieldLabelWidth?: string),在 TS 项目中使用 ConfigProvider 的theme-vars时能获得类型提示。
最佳实践小结
- 受控与异步提交:
contact-info作为初始数据源,配合is-saving实现"防重复提交 + loading 态";保存接口成功后再更新列表数据或跳转; - 校验交给组件:姓名必填、手机号格式均由内置 rules 完成,仅当业务规则不同(如海外手机号)时才覆盖
tel-validator; - 删除需二次确认:组件内部已接入删除确认对话框,业务回调中执行删除接口即可,无需重复弹窗;
- 与联系人列表联动:
ContactEditInfo结构与 ContactList 数据模型对齐,可在列表页选中后直接传入编辑页,保存后回写列表,形成完整闭环; - 样式定制优先走 CSS 变量:通过 ConfigProvider 或全局覆盖上述六个变量即可完成品牌化改造,无需侵入组件内部样式。
延伸阅读
- 组件完整源码:ContactEdit.tsx
- 组件类型定义:types.ts 与 index.ts
- 样式实现:index.less
- 测试用例:test/index.spec.ts、test/demo.spec.ts
- 配套组件:ContactList 联系人列表、ContactCard 联系人卡片
- 底层依赖:Form 表单、Field 输入框、ConfigProvider
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考