news 2026/9/23 8:04:27

Formily 异步数据源(dataSource)完整指南:在 effects 与 reactions 中动态管理下拉数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Formily 异步数据源(dataSource)完整指南:在 effects 与 reactions 中动态管理下拉数据
  • 前端
  • UI组件

【免费下载链接】formily

📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

导读

在 Formily 中,下拉选择、级联、树选择等数据型组件的选项数据并不需要你在组件层手写状态管理,而是统一收敛到字段模型(Field)的dataSource属性上。本指南以 async.zh-CN.md 为核心,系统讲解异步数据源的两种核心驱动方式(effects 与 reactions)、内置组件对dataSource的自动消费机制,以及业务自定义组件的手动映射方案,读完即可在 React 与 JSON Schema 场景下落地"接口返回选项、动态切换数据源"的完整实践。

核心模型:Field 的 dataSource 属性

异步数据源管理的核心载体是 Field 模型中的dataSource属性。Formily 的设计思想是:把"选项数据"当作字段的一种响应式状态,与valueloadingvalidating等字段状态同等对待,任何来源(异步请求、联动计算、外部注入)只要写入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/reactconnect+mapProps实现,各内置组件的映射关系如下(以 antd 包为例):

组件源码位置dataSource 映射到
Selectpackages/antd/src/select/index.tsxoptions,并同步映射loading
TreeSelectpackages/antd/src/tree-select/index.tsxtreeData
Cascaderpackages/antd/src/cascader/index.tsxoptions
Radiopackages/antd/src/radio/index.tsxoptions
Checkboxpackages/antd/src/checkbox/index.tsxoptions

以 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.dataSourcefield.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变更),组件自动重渲染——这与内置组件的行为完全一致,只是把"映射"这一步从框架代劳改成了自己声明。

响应式原理小结

整条链路可以归纳为:

  1. 写入:effects / reactions 中给field.dataSource赋值(或调用field.setDataSource(...));
  2. 通知dataSource作为observable.ref(见 Field.ts#L140),整体替换会触发依赖它的订阅者;
  3. 消费connect包装的组件通过mapPropsdataSource注入组件 props;observer组件则通过useField直接订阅;
  4. 渲染:组件收到新选项后自动重渲染,配合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

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 8:04:20

什么是翻新机?市政公用工程师的全栈避坑保姆级教程

什么是翻新机?市政公用工程师的全栈避坑保姆级教程 刚入行市政公用工程,是不是也遇到过这种绝望时刻:代码语法背得滚瓜烂熟, if-else 写了一堆,结果真要搭个智慧工地数据看板或者工程结算系统时,脑子一片空白? 别慌,这不是你的错。…

作者头像 李华
网站建设 2026/9/23 8:04:05

3个坑让设计师字体渲染慢5倍?这份高频面试题指南救急

3个坑让设计师字体渲染慢5倍?这份高频面试题指南救急 很多后端开发同学陷入一个怪圈:语法背得滚瓜烂熟,LeetCode 题也能刷,但真到了项目里,涉及“设计师字体”这种复杂文本渲染场景,代码一跑 CPU 飙红,内存泄漏,完全不知道从哪下手。更扎心的是,这恰恰是不少大厂面试里爱问的 高频面试题…

作者头像 李华
网站建设 2026/9/23 8:03:47

3步搞定发文字号格式:图解原理与避坑指南

3步搞定发文字号格式:图解原理与避坑指南 刚入行写公文,是不是总卡在格式上?明明背了规则,一到实战就乱套。别慌,咱们用图解原理的方式,把发文字号格式拆解得明明白白。 考点梳理:别把文号当乱码…

作者头像 李华
网站建设 2026/9/23 8:03:20

2026最新网站域名查询实战:3种方案对比解决项目落地难题

2026最新网站域名查询实战:3种方案对比解决项目落地难题 刚学会语法就急着上手,结果卡在“怎么查域名”这种基础操作上?这是很多开发者从教程走向真实项目时的第一道坎。别急,2026年最新的技术栈里,网站域名查询早已不是调个接口那么简单,而是涉及性能、成本与合规的选型问题。 一、三种主流查询方案定位…

作者头像 李华
网站建设 2026/9/23 8:03:12

SGM图解原理实战:3步搭好项目避坑

SGM图解原理实战:3步搭好项目避坑 刚学会Python语法,对着文档敲代码能跑通,但一让你搭个完整项目就脑子空白?别慌,这不是你笨,是缺了把零散知识串起来的逻辑。今天拿SGM(Statistical Grouping…

作者头像 李华
网站建设 2026/9/23 8:02:54

3步搞定领养孤儿机制,性能优化不再靠猜

3步搞定领养孤儿机制,性能优化不再靠猜 刚学完语法,打开IDE却对着空白页发呆?别慌,这毛病我当年也有。很多人卡在“知道怎么写if-else,却不知道怎么把数据从A库搬到B库还不掉链子”。今天咱们聊个冷门但救命的点: 领养孤儿 。这词听着像社工术语,其实在后端高并发场景下,它是指…

作者头像 李华