- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
在 Formily 中,下拉选择、级联、树选择等数据型组件的选项数据并不需要你在组件层手写状态管理,而是统一收敛到字段模型(Field)的dataSource属性上。本指南以 async.zh-CN.md 为核心,系统讲解异步数据源的两种核心驱动方式(effects 与 reactions)、内置组件对dataSource的自动消费机制,以及业务自定义组件的手动映射方案,读完即可在 React 与 JSON Schema 场景下落地"接口返回选项、动态切换数据源"的完整实践。
核心模型:Field 的 dataSource 属性
异步数据源管理的核心载体是 Field 模型中的dataSource属性。Formily 的设计思想是:把"选项数据"当作字段的一种响应式状态,与value、loading、validating等字段状态同等对待,任何来源(异步请求、联动计算、外部注入)只要写入dataSource,消费它的组件就会自动感知并重渲染。
从 Field 源码 可以看到其数据通路:
- 字段初始化时,
this.dataSource = this.props.dataSource,即支持通过x-component-props/component[1]或字段属性直接传入初始选项; makeObservable中将dataSource标记为observable.ref,意味着对它的整体替换会触发订阅者更新(这是"自动重渲染"的底层原理);- 模型上还暴露了命令式方法
setDataSource(dataSource?)(见 Field.ts#L385-L387),可在任意生命周期内以编程方式写入数据源。
dataSource的类型定义位于 core/src/types.ts,其结构为对象数组,且是递归自引用的,天然支持树形数据:
export type FieldDataSource = { label?: any value?: any title?: any key?: any text?: any children?: FieldDataSource [key: string]: any }[]这意味着:
- 扁平列表只需
{ label, value }结构; - 树形结构(TreeSelect、Cascader)通过
children递归嵌套; - 额外键(如禁用状态
disabled、图标等)会被透传,字段结构非常灵活。
方式一:在 effects 中修改 dataSource
effects 是什么
Formily 的 effects 是生命周期事件订阅机制,通过createForm的第二个参数传入,配合 onFieldEffects.ts 与 onFormEffects.ts 导出的各种事件钩子使用,例如:
onFieldInit/onFieldMount:字段初始化 / 挂载后触发;onFieldValueChange:字段值变化后触发;onFieldReact:字段反应器执行时触发;onFormMount:表单挂载后触发(常用来做整表数据的首次拉取)。
典型场景 1:字段挂载后拉取远程选项
import { createForm, onFieldInit, onFieldValueChange } from '@formily/core' const form = createForm({ effects() { // 字段挂载后,立即异步拉取该字段的选项 onFieldInit('select', async (field) => { field.loading = true // 进入加载态(Select 组件会展示 loading 图标) const res = await fetchOptions() field.dataSource = res.data // 写入 dataSource,组件自动重渲染 field.loading = false }) // 也可以响应其他字段变化,联动刷新数据源 onFieldValueChange('province', (field) => { const cityField = form.query('city').take() if (!cityField) return cityField.loading = true fetchCities(field.value).then((res) => { cityField.dataSource = res.data cityField.loading = false }) }) }, })典型场景 2:在表单挂载时统一初始化
如果多个字段共享同一份选项数据,可以挂在onFormMount中统一处理:
import { createForm, onFormMount } from '@formily/core' const form = createForm({ effects() { onFormMount(async () => { const res = await fetchAllDictionaries() form.query('role').take((field) => (field.dataSource = res.roles)) form.query('department').take((field) => (field.dataSource = res.departments)) }) }, })方式二:在 reactions 中修改 dataSource
reactions是字段模型上的响应式字段联动描述(类型见 IFieldProps.reactions,为(field: Field) => void或函数数组)。它与 effects 的区别在于:reactions 是声明在字段自身上的,跟随字段模型随 JSON Schema 一起被序列化描述,更适用于 Schema 化场景(如 JSON Schema 表单、设计器产物)。
import { createForm } from '@formily/core' const form = createForm({ fields: { province: { type: 'string', enum: provinceOptions, // 初始 dataSource }, city: { type: 'string', reactions(field) { // 当 province 变化时,根据其值异步刷新 city 的数据源 const province = field.query('province').get('value') if (!province) { field.dataSource = [] return } field.loading = true fetchCities(province).then((res) => { field.dataSource = res.data field.loading = false }) }, }, }, })在 React 中,reactions可以写在createSchemaField的组件 props 上或 JSON Schema 的x-reactions中;在核心层,字段初始化时会将props.reactions注册为字段的响应式依赖,任何被读取的字段状态变化都会重新执行该函数(对应onFieldReact生命周期)。
选择建议:逻辑偏"事件式"(如提交后刷新、定时轮询)用effects;逻辑偏"声明式联动"(依赖其他字段值、可随 Schema 分发)用reactions。两者都能修改
dataSource,也可以混用。
内置组件的自动消费:dataSource 到 props 的映射
文档中强调:如果字段组件内部有消费dataSource属性,当dataSource变化时组件会自动重渲染。这一能力由@formily/react的connect+mapProps实现,各内置组件的映射关系如下(以 antd 包为例):
| 组件 | 源码位置 | dataSource 映射到 |
|---|---|---|
| Select | packages/antd/src/select/index.tsx | options,并同步映射loading |
| TreeSelect | packages/antd/src/tree-select/index.tsx | treeData |
| Cascader | packages/antd/src/cascader/index.tsx | options |
| Radio | packages/antd/src/radio/index.tsx | options |
| Checkbox | packages/antd/src/checkbox/index.tsx | options |
以 Select 为例,select/index.tsx 中:
export const Select: ReactFC<SelectProps<any, any>> = connect( AntdSelect, mapProps( { dataSource: 'options', loading: true, }, ... ), mapReadPretty(PreviewText.Select) )mapProps({ dataSource: 'options', loading: true })的含义是:字段的dataSource会映射为 antd Select 的options,字段的loading/validating会映射为 loading 状态并渲染旋转图标。因此只要在 effects / reactions 中写入field.dataSource与field.loading,Select 就会自动刷新选项并展示加载动画,无需任何组件层手动 setState。
// 一个完整的最小示例:挂载后拉取选项 const form = createForm({ effects() { onFieldInit('select', (field) => { field.loading = true setTimeout(() => { field.dataSource = [ { label: '选项一', value: 1 }, { label: '选项二', value: 2 }, ] field.loading = false }, 500) }) }, })业务自定义组件:手动映射 dataSource
如果使用业务自定义组件(没有经过上述connect包装),dataSource不会自动成为组件的 props,必须手动映射。文档给出了两条路径:
路径 A:使用 connect + mapProps
用connect包装自定义组件,声明dataSource到组件 props 的映射关系:
import { connect, mapProps } from '@formily/react' const MySelect = connect( BaseSelect, // 你的业务组件,内部读取 props.options mapProps({ dataSource: 'options', // 字段 dataSource -> 组件 options loading: true, }) )之后在 Schema 中直接使用MySelect作为x-component,即可享受与内置 Select 一致的自动数据绑定。
路径 B:observer + useField 手动读取
如果组件结构复杂、不适合用connect映射,可以用observer包裹组件,并在组件内部通过useField拿到字段模型,主动读取field.dataSource并渲染:
import { observer, useField } from '@formily/react' const MySelect = observer(() => { const field = useField<any>() // 取到字段模型 return ( <BaseSelect options={field.dataSource} // 手动映射 loading={field.loading} /> ) })observer会让组件订阅字段模型的响应式状态,当field.dataSource被替换时(observable.ref变更),组件自动重渲染——这与内置组件的行为完全一致,只是把"映射"这一步从框架代劳改成了自己声明。
响应式原理小结
整条链路可以归纳为:
- 写入:effects / reactions 中给
field.dataSource赋值(或调用field.setDataSource(...)); - 通知:
dataSource作为observable.ref(见 Field.ts#L140),整体替换会触发依赖它的订阅者; - 消费:
connect包装的组件通过mapProps把dataSource注入组件 props;observer组件则通过useField直接订阅; - 渲染:组件收到新选项后自动重渲染,配合
field.loading展示加载状态。
这套机制让"异步数据源"在 Formily 中不需要任何额外的状态管理库,字段模型本身就是选项数据的唯一真源(Single Source of Truth),这也是其 JSON Schema 场景(json-schema 包)与 React/Vue 各渲染层(packages/react、packages/vue、packages/antd、packages/next)保持一致行为的基础。
进阶实践建议
- 统一字典服务:将高频字典选项抽成可复用的 effects 函数或自定义 Hook,在
onFieldInit中按field.path匹配字典 key 拉取,避免每个字段重复写请求逻辑; - 联动清空:当上游字段变化导致下游数据源失效时,记得同时清空下游
field.value(如cityField.setValue(undefined)),避免出现"选项已变、旧值仍残留"; - 错误兜底:异步请求失败时在 effects / reactions 的 catch 中设置
field.feedback(如field.setSelfErrors(['数据加载失败']))并关闭loading,保证表单不会卡在加载态; - 分页/搜索型下拉:对于远程搜索场景,可以结合
onFieldValueChange或组件自身的搜索事件,把关键字写入外部状态并驱动dataSource更新,结构与上述示例一致。
更多相关内置组件的具体行为可参考 antd 组件文档 中 Select、TreeSelect、Cascader 各章节,以及核心层 Field 模型文档。
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily Select 下拉框组件实战:同步/异步数据源与联动场景全解(@formily/antd)
Formily Select 下拉框组件实战:同步/异步数据源与联动场景全解(@formily/antd) Formily 是一套跨端、高性能的表单解决方案,支
前端UI组件管理业务逻辑:Formily 中 effects 与 reactions 的选型与实践指南
管理业务逻辑:Formily 中 effects 与 reactions 的选型与实践指南 Formily 2.x 在 docs/guide/advanced/
前端UI组件formily × Element UI(Vue 2)Select 下拉选择组件实战全指南:同步/异步数据源与联动方案
formily × Element UI(Vue 2)Select 下拉选择组件实战全指南:同步/异步数据源与联动方案 本指南以 @formily/elemen
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考