- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
ObjectField 是 Formily Vue 体系中用于将@formily/core的createObjectField字段模型与输入控件做绑定的桥接组件,专门解决"对象值"(即一个字段的值本身是一组键值对)这类动态结构表单的建模问题。阅读本文后,你将掌握 ObjectField 的完整签名、name与 scoped slot 的使用约束、动态增删对象属性的实战方案,以及它从 Vue 组件到 core 字段模型的完整实现链路。
ObjectField 是什么
packages/vue/docs/api/components/object-field.md中明确指出:ObjectField 是@formily/core的 createObjectField 的 Vue 实现,它的职责是"将 ViewModel 与输入控件做绑定的桥接组件"。通俗地说:
- 普通
Field绑定的值是一个标量(字符串、数字等); ArrayField绑定的值是一个数组;ObjectField绑定的值是一个对象,例如{ title: 'foo', desc: 'bar' },且对象的键是动态的、运行期可以增删的。
因此,凡是"属性集合不固定、需要动态管理"的表单场景——例如动态标签管理、自定义属性编辑器、键值对配置面板——都是 ObjectField 的典型应用场景。它的字段属性(field.value、field.addProperty、field.removeProperty等)参考IFieldFactoryProps约定,在 Vue 侧对应的类型定义可以在 packages/vue/src/types/index.ts 中找到。
组件签名与属性类型
原文档给出的签名如下:
type ObjectField = Vue.Component<any, any, any, IFieldFactoryProps>在 Vue 包的实际类型定义中,这一签名被落实为:
export type IObjectFieldProps = IFieldProps即 packages/vue/src/types/index.ts 中,IObjectFieldProps直接复用了 core 的IFieldProps(name、basePath、value、initialValue、validator、required、pattern、display、decorator、component、reactions、visible等通用字段属性)。同时,从 packages/vue/src/vue2-components.ts 可以看到,Omit<IObjectFieldProps, 'name'>还用于派生 JSON Schema 场景下的字段工厂类型,说明 ObjectField 的属性体系与普通 Field 保持一致,区别仅在于它创建的是对象型字段模型。
使用 ObjectField 的两个硬性约束
原文档以::: warning的形式特别强调了两条使用纪律,这也是新手最容易踩的坑:
- 一定要传
name属性:ObjectField 需要通过name定位它在表单值树中的路径。省略name时字段地址无法正确解析,表单值的读写与校验都会失焦。 - 必须使用 scoped slot 形式组织子组件:只有通过
#default="{ field }"这样的作用域插槽,才能在子组件内部拿到当前字段的field实例,进而访问field.value、field.addProperty、field.removeProperty等对象字段专属能力。
此外,ObjectField 与Field一样,需要嵌套在 FormProvider 之下使用,以继承表单上下文(useForm/useField依赖注入的来源)。
完整用例:动态增删对象属性
原文档通过<dumi-previewer demoPath="api/components/object-field" />引用了一个可运行示例,其实际源码位于 packages/vue/docs/demos/api/components/object-field.vue。这是一个非常典型的"对象属性编辑器":左侧遍历并渲染field.value中的每一个键值输入框,右侧提供一个输入框 + 按钮用于动态追加新属性。完整代码如下:
<template> <FormProvider :form="form"> <ObjectField name="object"> <template #default="{ field }"> <div v-for="key in Object.keys(field.value || {})" :key="key" :style="{ marginBottom: '10px' }" > <Space> <Field :name="key" :component="[Input, { placeholder: key }]" /> <Button @click="field.removeProperty(key)"> Remove </Button> </Space> </div> <Space> <Field name="propertyName" basePath="" required :component="[Input, { placeholder: 'Property Name' }]" /> <Button @click="addPropertyToField(field)"> Add </Button> </Space> </template> </ObjectField> </FormProvider> </template> <script> import { Input, Space, Button } from 'ant-design-vue' import { createForm } from '@formily/core' import { FormProvider, ObjectField, Field } from '@formily/vue' import 'ant-design-vue/dist/antd.css' export default { components: { FormProvider, ObjectField, Field, Space, Button }, data() { return { Input, form: createForm(), } }, methods: { addPropertyToField(field) { const name = this.form.values.propertyName if (name && !this.form.existValuesIn(`object.${name}`)) { field.addProperty(name, '') this.form.deleteValuesIn('propertyName') } }, }, } </script>逐段拆解这个用例,你可以看到几个关键设计:
- 作用域插槽拿字段实例:
<template #default="{ field }">中的field就是 core 的ObjectField模型实例,field.value返回当前路径下的整个对象(内部实现见下文"值即路径读取")。 - 遍历渲染:
Object.keys(field.value || {})动态列出所有键,为每个键渲染一个Field。注意这里子Field只传了name,它会自动基于父字段地址拼接路径(例如object.title),这正是"父字段提供 basePath"机制的体现。 - 删除属性:
field.removeProperty(key)直接删除该键对应的值,同时清理该键下挂载的所有子字段。 - 新增属性:先从
form.values.propertyName读取要新增的键名,用form.existValuesIn('object.' + name)做重名防抖,通过field.addProperty(name, '')写入空值,最后form.deleteValuesIn('propertyName')清空输入框——这构成一个完整的"输入键名 → 追加键值对 → 清空输入"闭环。 - 新增输入框放在表单根部:
basePath=""让propertyName这个输入字段落在表单顶层(/propertyName),而不是object.propertyName,从而避免与 ObjectField 管理的对象相互干扰。
源码级原理:从 ObjectField 到 createObjectField
Vue 侧 packages/vue/src/components/ObjectField.ts 的代码非常薄,它同时兼容 Vue 2 与 Vue 3(基于 vue-demi):
- Vue 3 分支:
setup(props, context)中把fieldType: 'ObjectField'与合并后的fieldProps(包含getFieldProps()声明的 props 与getRawComponent(props)抽取的原始组件)一起交给ReactiveField渲染; - Vue 2 分支:以
functional: true函数式组件的形式完成同样的转发,context.children作为插槽内容透传。
真正的字段创建发生在 packages/vue/src/components/ReactiveField.ts 中:它通过useForm()拿到表单实例,然后执行:
formRef?.value?.[`create${props.fieldType}`]?.({ ...props.fieldProps, basePath })当fieldType为ObjectField时,即调用form.createObjectField(...)。basePath默认取自父字段地址(parentRef.value?.address),这就是子Field只传name就能自动拼出完整路径的底层原因。ReactiveField还通过useAttach(fieldRef)将字段挂载进表单,并通过provide(FieldSymbol, fieldRef)向子孙组件提供字段上下文;在渲染阶段,mergeSlots会把插槽参数统一补上{ field, form: field.form, ...originArgs[0] },这正是 scoped slot 中能同时拿到field与form的来源。
在 core 侧,packages/core/src/models/ObjectField.ts 中的ObjectField类继承自Field,其值读取直接映射到表单值树:
get value(): ValueType { return this.form.getValuesIn(this.path) // packages/core/src/models/Field.ts#L326 }也就是说,field.value不是字段自身缓存的数据,而是表单values中object路径下的实时对象——所有子字段的输入都会立刻反映到field.value上,这也是示例中v-for="key in Object.keys(field.value || {})"能自动响应的根本原因。
ObjectField 专属 API:addProperty / removeProperty / existProperty
在 packages/core/src/models/ObjectField.ts 中,ObjectField 相比普通 Field 新增了三个实例方法:
| 方法 | 签名 | 行为说明 | 源码位置 |
|---|---|---|---|
addProperty | (key: string, value: any) => void | 通过form.setValuesIn(this.path.concat(key), value)写入键值,并记录到additionalProperties清单,最后触发onInput | ObjectField.ts |
removeProperty | (key: string) => void | 通过form.deleteValuesIn(this.path.concat(key))删除键值,同时从additionalProperties移除该键 | ObjectField.ts |
existProperty | (key: string) => boolean | 通过form.existValuesIn(this.path.concat(key))判断属性是否存在 | ObjectField.ts |
注意addProperty/removeProperty操作的是表单值树(setValuesIn/deleteValuesIn),而不是字段本地状态,因此对field.value的变更天然响应式,界面会立刻重渲染。
自动化清理机制:makeAutoCleanable
ObjectField 还内置了一个容易被忽视但非常重要的特性——makeAutoCleanable(ObjectField.ts):
protected makeAutoCleanable() { this.disposers.push( reaction( () => Object.keys(this.value || {}), (newKeys) => { const filterKeys = this.additionalProperties.filter( (key) => !newKeys.includes(key) ) cleanupObjectChildren(this, filterKeys) } ) ) }它通过@formily/reactive的reaction监听当前对象的键集合变化:当某个键从field.value中消失(例如被外部form.deleteValuesIn删除、或被联动逻辑清空)时,会自动清理该键下挂载的所有子字段模型(cleanupObjectChildren),避免出现"值已删除、字段模型仍残留"导致的脏状态与内存泄漏。这意味着你用addProperty动态追加的字段,其生命周期完全由 ObjectField 托管,无需手动销毁。
测试验证与更多场景
Vue 包针对 ObjectField 的绑定行为有对应的单元测试覆盖,例如 packages/vue/src/tests/field.spec.ts 与 packages/vue/src/tests/schema.markup.spec.ts 中均涉及 ObjectField 的创建与渲染断言,可作为阅读和调试的参考入口。
在 Formily 的字段家族中,ObjectField 与 ArrayField(数组型)、VoidField(无值容器型)共同覆盖了复杂嵌套结构的建模需求。如果你的数据是一个"键集合固定"的普通对象,直接用Field+ JSON Schema 即可;只有当键需要在运行期动态增删、或需要对对象整体做联动与校验控制时,才需要引入 ObjectField。
小结
ObjectField 是 Formily Vue 处理动态对象结构的核心桥接组件:使用上牢记"必传name+ scoped slot 组织子组件"两条铁律,实战中借助field.value遍历、addProperty/removeProperty增删、existProperty判重即可搭建完整属性编辑器;原理上,它经由 ObjectField.ts →ReactiveField→form.createObjectField完成字段模型创建,由 core 的ObjectField类提供值映射、动态增删与自动清理能力,是一套"薄封装、强模型"的典型实现。
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily React ObjectField 组件深度指南:动态对象字段建模、渲染与源码原理剖析
Formily React ObjectField 组件深度指南:动态对象字段建模、渲染与源码原理剖析 ObjectField 是 Formily 面向 Rea
前端UI组件Formily Core ObjectField 模型完全指南:对象字段的动态属性增删与状态管理
Formily Core ObjectField 模型完全指南:对象字段的动态属性增删与状态管理 ObjectField 是 Formily 核心模型体系中专门
前端UI组件Formily React 字段桥接组件 Field 完全指南:ViewModel 与输入控件的绑定原理与实战
Formily React 字段桥接组件 Field 完全指南:ViewModel 与输入控件的绑定原理与实战 导读 在 Formily 的 React 体系中
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考