news 2026/9/23 16:21:32

Formily 单选框(Radio)组件完全指南:三种 Schema 写法与源码级原理剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Formily 单选框(Radio)组件完全指南:三种 Schema 写法与源码级原理剖析
  • 前端
  • 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 体系中@formily/next的 Radio 组件文档为核心,系统讲解如何在表单中实现单选功能。你将掌握 Markup Schema、JSON Schema、纯 JSX 三种声明方式下 Radio 的完整用法,理解enum/dataSource选项数据的映射机制,并深入connect/mapProps/mapReadPretty等源码层实现,搞清楚单选值如何与表单模型双向同步、如何自动支持只读预览,从而在实际项目中游刃有余地选用最合适的写法。

一、组件定位:Formily 中的单选组件

Radio(单选框)是表单中最基础的选择类组件之一,用于在若干互斥选项中仅选取一项。在 Formily 体系中,各 UI 库适配层均提供了统一的Radio组件接口,但它们底层渲染的具体控件不同:

  • @formily/next:基于阿里巴巴 Fusion Next;
  • @formily/antd:基于 Ant Design 的Radio,源码位于 packages/antd/src/radio/index.tsx;
  • @formily/element:基于 Element UI 的Radio/RadioButton,源码位于 packages/element/src/radio/index.ts。

本文以 packages/next/docs/components/Radio.md 文档为主展开,其余实现作为对照参考。所有适配层都遵循同一个设计模式:把第三方 UI 组件的 Props 通过connect桥接到 Formily 的字段模型上,因此无论你使用哪套组件库,写法与行为都高度一致。

文档中组件被定义为 "Single selection box"(单选选择框),并声明了两种形态:

形态说明
Radio单个单选项(对应底层NextRadio/AntdRadio/ElRadio
Radio.Group单选组(对应底层NextRadio.Group/AntdRadio.Group/ElRadioGroup),配合选项数据一次性渲染一组单选

在实际业务中,Radio.Group是更常用的形态,因为它直接对接选项数据源,与表单字段的值绑定。

二、Markup Schema 写法:声明式组件树

Markup Schema 是 Formily 在 JSX 内部书写 JSON Schema 的 DSL 形式。它把 Schema 节点写成组件标签(如<SchemaField.Number />),由编译器在运行时转换回标准 Schema 结构。

文档中的示例完整代码如下(源码见 packages/next/docs/components/Radio.md):

import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() export default () => ( <FormProvider form={form}> <SchemaField> <SchemaField.Number name="radio" title="single choice" enum={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} x-decorator="FormItem" x-component="Radio.Group" /> </SchemaField> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> )

关键点拆解:

  1. 注册组件createSchemaField({ components: { Radio, FormItem } })RadioFormItem注册进 SchemaField 的组件映射表,这样x-component="Radio.Group"x-decorator="FormItem"才能在运行时被正确解析。
  2. 字段类型SchemaField.Number表明该字段的数据类型为number,这也是单选值常用的类型(选项 value 为 1、2)。
  3. 选项数据enum属性传入{ label, value }结构数组。在 Schema 编译阶段,enum会被自动映射为字段的dataSource(见后文「enum 与 dataSource 的映射原理」),最终透传给Radio.Group渲染。
  4. 装饰器与组件x-decorator="FormItem"表示用 FormItem 包裹(负责布局、标签、校验错误展示),x-component="Radio.Group"指定渲染单选组。
  5. 提交Submit配合FormButtonGroup组成提交按钮,onSubmit={console.log}会在提交时打印表单值,此时form.values中的radio字段即为选中项的 value(如12)。

三、JSON Schema 写法:纯数据驱动

JSON Schema 写法与 Markup Schema 在语义上完全等价,只是把 Schema 从 JSX 中抽离为独立的 JSON 对象,适合动态化、服务端下发的场景。文档示例(见 packages/next/docs/components/Radio.md):

import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, createSchemaField } from '@formily/react' const SchemaField = createSchemaField({ components: { Radio, FormItem, }, }) const form = createForm() const schema = { type: 'object', properties: { radio: { type: 'number', title: 'Single selection', enum: [ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, }, } export default () => ( <FormProvider form={form}> <SchemaField schema={schema} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> )

与 Markup Schema 的差异仅在声明形式上:

  • Markup 写法用<SchemaField.Number name="radio" ... />
  • JSON 写法用properties.radio = { type: 'number', ... }

两者在运行时生成完全一致的字段模型。createSchemaField负责将 JSON Schema 编译为可被@formily/react消费的内部结构,enum同样会被转换为dataSource注入字段。

四、纯 JSX 写法:完全编程式控制

如果不需要 Schema 的声明式能力(例如选项数据来自运行时计算、或需要精细控制组件 Props),可以直接使用Field组件以 JSX 方式编写。文档示例(见 packages/next/docs/components/Radio.md):

import React from 'react' import { Radio, FormItem, FormButtonGroup, Submit } from '@formily/next' import { createForm } from '@formily/core' import { FormProvider, Field } from '@formily/react' const form = createForm() export default () => ( <FormProvider form={form}> <Field name="radio" title="single choice" dataSource={[ { label: 'Option 1', value: 1, }, { label: 'Option 2', value: 2, }, ]} decorator={FormItem} component={Radio.Group} /> <FormButtonGroup> <Submit onSubmit={console.log}>Submit</Submit> </FormButtonGroup> </FormProvider> )

注意纯 JSX 写法与 Schema 写法的对应关系:

Schema 属性JSX 属性说明
enumdataSource选项数据源,JSX 中直接使用字段模型的概念
x-decorator="FormItem"decorator={FormItem}装饰器组件,直接传组件引用而非字符串
x-component="Radio.Group"component={Radio.Group}渲染组件,直接传组件引用

Field组件把nametitledataSource等属性写入字段模型,并将Radio.Group作为实际渲染组件,同时由FormItem装饰。三者中纯 JSX 写法最贴近传统 React 组件用法,也是理解connect原理最直观的入口。

五、enum 与 dataSource 的映射原理

为什么 Schema 里写的是enum,而 JSX 里写的是dataSource?这是 Formily 刻意设计的标准 JSON Schema 关键字到内部状态模型的关键字映射

在 packages/json-schema/src/shared.ts 中定义了SchemaStateMap

export const SchemaStateMap = { title: 'title', description: 'description', default: 'initialValue', enum: 'dataSource', // ← enum 映射为 dataSource readOnly: 'readOnly', writeOnly: 'editable', 'x-content': 'content', 'x-data': 'data', 'x-value': 'value', 'x-editable': 'editable', 'x-disabled': 'disabled', 'x-read-pretty': 'readPretty', 'x-read-only': 'readOnly', 'x-visible': 'visible', 'x-hidden': 'hidden', 'x-display': 'display', 'x-pattern': 'pattern', 'x-validator': 'validator', 'x-decorator': 'decoratorType', 'x-component': 'componentType', 'x-decorator-props': 'decoratorProps', 'x-component-props': 'componentProps', }

Schema 编译时,enum键会被翻译为字段状态中的dataSource。同文件中的patchStateFormSchema(packages/json-schema/src/shared.ts)进一步处理了enum数组的标准化:

const isEnum = key === 'enum' && isArr(compiled) // ... isEnum ? createDataSource(compiled) : compiled

createDataSource(packages/json-schema/src/shared.ts)会把纯字符串/数字数组自动包装成{ label, value }结构:

export const createDataSource = (source: any[]) => { return toArr(source).map((item) => { if (typeof item === 'object') { return item } else { return { label: item, value: item, } } }) }

这意味着你既可以写enum={[{ label: 'Option 1', value: 1 }]}这样的完整对象数组,也可以简写为enum={['Option 1', 'Option 2']},后者会被自动补齐为{ label: 'Option 1', value: 'Option 1' }。这与@formily/next组件层mapProps({ dataSource: true })的透传配合,最终将标准化的选项数组送入 Fusion Next 的Radio.Group

六、组件源码解析:connect 如何打通双向绑定

RadioRadio.Group的本质是@formily/reactconnect高阶组件包装。以 packages/next/src/radio/index.tsx 为例:

export const Radio: ComposedRadio = connect( NextRadio, mapProps( { value: 'checked', }, mapSize ) ) Radio.Group = connect( NextRadio.Group, mapProps( { dataSource: true, }, mapSize ), mapReadPretty(PreviewText.Select) )

1. 单个 Radio 的 value → checked 映射

mapProps({ value: 'checked' })将 Formily 字段模型的value映射为 Fusion Next 单选项的checkedprop。单个Radio通常用于固定布尔型或独立开关场景(如"是否同意协议"),选中时checked = true会通过onChange回写字段值。

2. Radio.Group 的 dataSource 透传与尺寸联动

mapProps({ dataSource: true })表示将字段的dataSource原样透传给Radio.Group。结合上文可知,无论选项来自 Schema 的enum还是 JSX 的dataSource,最终都会落在这个属性上。

第二个映射mapSize来自 packages/next/src/builtins/mapSize.ts:

export const mapSize = (props: any) => { const layout = { ...useFormShallowLayout(), ...useFormLayout() } const takeSize = () => { return layout.size === 'default' ? 'medium' : layout.size } return { ...props, size: props.size || takeSize(), } }

mapSize从表单布局上下文(FormLayout)读取全局尺寸配置并注入组件,使 Radio 能跟随表单统一调整大小(small/medium/large),无需在每个组件上手动声明。这正是 Formily 全局布局能力的体现,详见 packages/next/src/form-layout/index.tsx。

3. mapReadPretty:自动只读预览

mapReadPretty(PreviewText.Select)是单选组在只读态(如详情页、表单回显)下的关键实现。当字段处于readPretty模式时,connect会自动用PreviewText.Select替换Radio.Group的渲染。PreviewText.Select的实现位于 packages/next/src/preview-text/index.tsx:

const Select: React.FC<React.PropsWithChildren<SelectProps>> = observer( (props) => { const field = useField<Field>() // ... const dataSource: any[] = field?.dataSource?.length ? field.dataSource : props?.dataSource?.length ? props.dataSource : [] const placeholder = usePlaceholder() const getLabel = (target: any) => { return ( dataSource?.find((item) => item.value == target?.value)?.label || target.label || placeholder ) } // ... return <div className={cls(prefixCls, props.className)}>{getLabels()}</div> } )

它通过useField读取字段模型上的dataSource,把当前选中的value反查为对应的label并以纯文本渲染(无值时显示默认占位符N/A)。这样在只读场景下,单选值展示为可读文案而非被禁用的控件。占位符可通过PreviewText.Placeholder全局定制,例如设置为'暂无'

4. Antd 版本对照

packages/antd/src/radio/index.tsx 与 Next 版本高度一致,差异仅在适配层细节:

export const Radio: ComposedRadio = connect( AntdRadio, mapProps({ value: 'checked', }) ) Radio.__ANT_RADIO = true Radio.Group = connect( AntdRadio.Group, mapProps({ dataSource: 'options', // Antd 的选项属性名是 options }), mapReadPretty(PreviewText.Select) )
  • Antd 版本额外设置了Radio.__ANT_RADIO = true标记,供内部逻辑识别组件来源;
  • dataSource: 'options'将字段的dataSource映射为 Antd Radio.Group 的options属性,而 Next 版本是dataSource: true原样透传——这是两套组件库 API 差异在适配层被抹平的直接证据;
  • 只读预览同样使用PreviewText.Select,对应实现在 packages/antd/src/preview-text/index.tsx,额外支持fieldNames自定义label/value字段名。

Element 版本(Vue 场景)见 packages/element/src/radio/index.ts,它额外实现了options渲染逻辑与optionType: 'default' | 'button'的按钮形态切换(RadioButton),且选项值为label,同样以mapProps({ dataSource: 'options' })mapReadPretty(PreviewText.Select)完成桥接。

七、API 说明与扩展属性

文档末尾给出的 API 说明为:

Reference https://fusion.design/pc/component/basic/radio

@formily/nextRadio直接继承 Fusion Next Radio 的完整 API,包括Radio.GroupdataSourcevalueonChangedisabledsize等全部属性。Formily 适配层不改变底层组件的 API 语义,只是把字段的值/数据源/状态与组件对接,因此底层组件的任何能力(如Radio.Group的方向布局、按钮样式等)均可直接使用。

在此基础上,Formily 还叠加了下列由适配层提供的增强能力:

能力实现机制说明
字段值双向同步connect+mapProps({ value: 'checked' })选中状态与字段模型value实时同步
选项数据自动注入mapProps({ dataSource: true / 'options' })从字段dataSource映射到底层组件的选项属性
尺寸联动mapSize(仅 Next)跟随FormLayout全局尺寸自动调整
只读预览mapReadPretty(PreviewText.Select)readPretty模式下渲染为纯文本 label
校验与错误展示x-decorator="FormItem"的字段联动单选必选校验通过 FormItem 呈现

必选校验示例

结合FormItem装饰器,只需为字段声明required即可启用单选必选校验:

<SchemaField.Number name="radio" title="single choice" required enum={[ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 }, ]} x-decorator="FormItem" x-component="Radio.Group" />

未选择任何选项时提交,FormItem 会展示"该字段必填"的校验提示,Submit也不会触发onSubmit回调。

八、三种写法的选型建议

写法适用场景优势注意点
Markup Schema常规表单开发,希望 Schema 与 JSX 共处类型友好、IDE 提示好、可读性高createSchemaField注册组件
JSON Schema动态表单、服务端下发 Schema、低代码平台纯数据可序列化、易于持久化与远程传输Schema 需自行维护与校验
纯 JSX逻辑复杂、选项运行时计算、精细控制组件无 Schema 心智负担、最接近原生 React 用法数据源需显式传dataSource

三者最终都会汇入@formily/core的字段模型,通过FormProvider共享同一个form实例,因此校验、联动、提交等能力完全一致,可以在同一表单内混用。

九、小结

本文围绕 packages/next/docs/components/Radio.md 的三个完整示例,完整呈现了@formily/next中 Radio 单选框的三种声明方式:

  1. Markup Schema<SchemaField.Number x-component="Radio.Group" enum={...} />
  2. JSON Schemaproperties.radio = { type: 'number', enum: [...], 'x-component': 'Radio.Group' }
  3. 纯 JSX<Field component={Radio.Group} dataSource={...} />

并从源码层验证了三个关键机制:enum → dataSource的标准 JSON Schema 关键字映射(packages/json-schema/src/shared.ts)、connect/mapProps/mapSize的双向绑定与尺寸联动(packages/next/src/radio/index.tsx、packages/next/src/builtins/mapSize.ts)、以及mapReadPretty(PreviewText.Select)的只读文本预览(packages/next/src/preview-text/index.tsx)。这些机制在@formily/antd@formily/element中同样成立,只是底层映射的组件属性名略有差异。

掌握了 Radio 的用法,也就掌握了 Formily 中所有"选项型"组件(Select、Checkbox、Cascader 等)的通用接入范式——它们共享同一套dataSource数据契约与connect桥接模型,可以举一反三。

  • 前端
  • 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 16:20:43

DeepSeek+向量数据库:企业知识库搭建实践与避坑指南

简介&#xff1a;面向希望借助大模型与向量检索技术构建企业知识管理系统的开发者&#xff0c;这份PDF以DeepSeek与向量数据库为主线&#xff0c;系统梳理了从基础原理到落地实现的完整链路。文档覆盖DeepSeek技术概述、向量数据库核心概念与Faiss/Milvus/Pinecone对比、企业知…

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

自组织映射SOM算法:无监督聚类与拓扑可视化实战

简介&#xff1a;这份资源提供完整的自组织映射&#xff08;SOM&#xff09;算法Python实现&#xff0c;面向机器学习初学者及需要聚类、降维与可视化实践的开发者。SOM作为经典无监督神经网络&#xff0c;可将高维数据映射到二维网格并保持拓扑结构&#xff0c;代码覆盖网络初…

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

U-Net裂缝检测实战:端到端识别0.2mm混凝土微裂纹

简介&#xff1a;本资源是一套基于深度学习的裂缝检测技术完整实现方案&#xff0c;面向计算机、人工智能、土木工程及自动化等专业的在校学生、教师与初级工程师&#xff0c;适用于课程设计、毕业设计、科研入门及工业缺陷检测场景。压缩包共3个文件&#xff0c;含2个核心Pyth…

作者头像 李华
网站建设 2026/9/23 16:17:40

5个维度教你选对人才测评工具,告别2026选型困难

一、选型这件事&#xff0c;别再只看“量表好不好”2026年企业数字化进入深水区&#xff0c;人才测评工具的选择难度反而更大了。市面上的系统从国际老牌到本土新锐&#xff0c;功能描述趋同&#xff0c;价格跨度悬殊。很多HR在选型时容易陷入两个极端&#xff1a;要么被大牌光…

作者头像 李华
网站建设 2026/9/23 16:15:25

知识工作插件化:从机制原理到高效工作流搭建

1. 知识工作的插件化思路&#xff1a;从工具集成到效率跃迁知识工作&#xff08;knowledge work&#xff09;从来不缺工具&#xff0c;缺的是把工具串起来的那个“连接器”。我见过太多人电脑里装了一堆软件&#xff0c;写文档用一个地方&#xff0c;查资料换一个地方&#xff…

作者头像 李华
网站建设 2026/9/23 16:15:00

DeepSeek生成JSON转MIDI:AI作曲数据清洗与Python落地全流程

简介&#xff1a;面向AI音乐创作者与有一定编程基础的技术开发者&#xff0c;这份PDF围绕“DeepSeekMIDI”提供了一套从零生成原创音乐的完整实战方案。文档共26页&#xff0c;先梳理AI作曲背景与DeepSeek能力特点&#xff0c;再深入讲解MIDI文件头、轨道、事件结构及解析方法&…

作者头像 李华