文章目录
- 1、Formily表单介绍
- 2、安装依赖
- 2.1、安装内核库
- 2.2、 安装 UI 桥接库
- 2.3、Formily 支持多种 UI 组件生态:
- 3、表单设计器
- 3.1、核心理念
- 3.2、安装
- 3.3、示例源码
- 4、场景案例-登录注册
- 4.1、Markup Schema 案例
- 4.2、JSON Schema 案例
- 4.3、纯 JSX 案例
1、Formily表单介绍
Formily 是一个由阿里开源的动态表单解决方案,主要用于构建和管理复杂的表单界面。支持多种前端框架,包括但不限于 React 和 Vue,支持图形可视化界面设计表单,支持多种 UI 组件集成,Formily 的核心优势在于其灵活性和扩展性,允许开发者以声明式的方式定义表单结构和行为。
同类表单产品比较:
能力 | Ant Design Form | Fusion Form | Formik | React Final Form | React Schema Form | React Hook Form | Formily1.x | Formily2.x |
---|---|---|---|---|---|---|---|---|
自定义组件接入成本 | 4.x接入成本低 | 高 | 低 | 低 | 高 | 高 | 低 | 低 |
性能 | 4.x性能较好 | 差 | 差 | 较好 | 差 | 好 | 非常好 | 非常好 |
是否支持动态渲染 | 否 | 否 | 否 | 否 | 是 | 否 | 是 | 是 |
是否开箱即用 | 是 | 是 | 否 | 否 | 是 | 否 | 是 | 是 |
是否支持跨端 | 否 | 否 | 否 | 否 | 否 | 否 | 是 | 是 |
开发效率 | 一般 | 一般 | 一般 | 一般 | 低 | 一般 | 高 | 高 |
学习成本 | 低 | 低 | 低 | 高 | 高 | 低 | 很高 | |
视图代码可维护性 | 低 | 低 | 低 | 低 | 高 | 低 | 高 | 高 |
场景化封装能力 | 无 | 无 | 无 | 无 | 有 | 无 | 有 | 有 |
是否支持表单预览态 | 否 | 是 | 否 | 否 | 否 | 否 | 是 | 是 |
2、安装依赖
2.1、安装内核库
使用 Formily 必须要用到@formily/core,它负责管理表单的状态,表单校验,联动等等。
npm install --save @formily/core
2.2、 安装 UI 桥接库
单纯有了内核还不够,我们还需要一个 UI 库来接入内核数据,用来实现最终的表单交互效果,对于不同框架的用户,我们有不同的桥接库。
Vue 用户
npm install --save @formily/vue
2.3、Formily 支持多种 UI 组件生态:
包含 @formily/antd、@formily/antd-v5、@formily/antd-mobile、@formily/next、@formily/element、@formily/element-plus、@formily/antdv、@formily/vant 、@formily/semi、@formily/tdesign-react、aliyun teamix、antd-formily-boost。
3、表单设计器
Formily 表单设计器是基于designable而扩展出来的扩展包,它在继承了 designable 的基础能力上,提供了 Formily 基础表单的搭建和配置能力。
3.1、核心理念
Designable 的核心理念是将设计器搭建变成模块化组合,一切可替换,Designable 本身提供了一系列开箱即用的组件给用户使用,但是如果用户对组件不满意,是可以直接替换组件,从而实现最大化灵活定制,也就是 Designable 本身是不会提供任何插槽 Plugin 相关的 API
3.2、安装
Ant Design 用户
npm install --save @designable/formily-antd
Alibaba Fusion 用户
npm install --save @designable/formily-next
3.3、示例源码
import 'antd/dist/antd.less'
import React, { useMemo } from 'react'
import ReactDOM from 'react-dom'
import {
Designer, //设计器根组件,主要用于下发上下文
DesignerToolsWidget, //画板工具挂件
ViewToolsWidget, //视图切换工具挂件
Workspace, //工作区组件,核心组件,用于管理工作区内的拖拽行为,树节点数据等等...
OutlineTreeWidget, //大纲树组件,它会自动识别当前工作区,展示出工作区内树节点
ResourceWidget, //拖拽源挂件
HistoryWidget, //历史记录挂件
StudioPanel, //主布局面板
CompositePanel, //左侧组合布局面板
WorkspacePanel, //工作区布局面板
ToolbarPanel, //工具栏布局面板
ViewportPanel, //视口布局面板
ViewPanel, //视图布局面板
SettingsPanel, //右侧配置表单布局面板
ComponentTreeWidget, //组件树渲染器
} from '@designable/react'
import { SettingsForm } from '@designable/react-settings-form'
import {
createDesigner,
GlobalRegistry,
Shortcut,
KeyCode,
} from '@designable/core'
import {
LogoWidget,
ActionsWidget,
PreviewWidget,
SchemaEditorWidget,
MarkupSchemaWidget,
} from './widgets'
import { saveSchema } from './service'
import {
Form,
Field,
Input,
Select,
TreeSelect,
Cascader,
Radio,
Checkbox,
Slider,
Rate,
NumberPicker,
Transfer,
Password,
DatePicker,
TimePicker,
Upload,
Switch,
Text,
Card,
ArrayCards,
ObjectContainer,
ArrayTable,
Space,
FormTab,
FormCollapse,
FormLayout,
FormGrid,
} from '../src'
GlobalRegistry.registerDesignerLocales({
'zh-CN': {
sources: {
Inputs: '输入控件',
Layouts: '布局组件',
Arrays: '自增组件',
Displays: '展示组件',
},
},
'en-US': {
sources: {
Inputs: 'Inputs',
Layouts: 'Layouts',
Arrays: 'Arrays',
Displays: 'Displays',
},
},
})
const App = () => {
const engine = useMemo(
() =>
createDesigner({
shortcuts: [
new Shortcut({
codes: [
[KeyCode.Meta, KeyCode.S],
[KeyCode.Control, KeyCode.S],
],
handler(ctx) {
saveSchema(ctx.engine)
},
}),
],
rootComponentName: 'Form',
}),
[]
)
return (
<Designer engine={engine}>
<StudioPanel logo={<LogoWidget />} actions={<ActionsWidget />}>
<CompositePanel>
<CompositePanel.Item title="panels.Component" icon="Component">
<ResourceWidget
title="sources.Inputs"
sources={[
Input,
Password,
NumberPicker,
Rate,
Slider,
Select,
TreeSelect,
Cascader,
Transfer,
Checkbox,
Radio,
DatePicker,
TimePicker,
Upload,
Switch,
ObjectContainer,
]}
/>
<ResourceWidget
title="sources.Layouts"
sources={[
Card,
FormGrid,
FormTab,
FormLayout,
FormCollapse,
Space,
]}
/>
<ResourceWidget
title="sources.Arrays"
sources={[ArrayCards, ArrayTable]}
/>
<ResourceWidget title="sources.Displays" sources={[Text]} />
</CompositePanel.Item>
<CompositePanel.Item title="panels.OutlinedTree" icon="Outline">
<OutlineTreeWidget />
</CompositePanel.Item>
<CompositePanel.Item title="panels.History" icon="History">
<HistoryWidget />
</CompositePanel.Item>
</CompositePanel>
<Workspace id="form">
<WorkspacePanel>
<ToolbarPanel>
<DesignerToolsWidget />
<ViewToolsWidget
use={['DESIGNABLE', 'JSONTREE', 'MARKUP', 'PREVIEW']}
/>
</ToolbarPanel>
<ViewportPanel>
<ViewPanel type="DESIGNABLE">
{() => (
<ComponentTreeWidget
components={{
Form,
Field,
Input,
Select,
TreeSelect,
Cascader,
Radio,
Checkbox,
Slider,
Rate,
NumberPicker,
Transfer,
Password,
DatePicker,
TimePicker,
Upload,
Switch,
Text,
Card,
ArrayCards,
ArrayTable,
Space,
FormTab,
FormCollapse,
FormGrid,
FormLayout,
ObjectContainer,
}}
/>
)}
</ViewPanel>
<ViewPanel type="JSONTREE" scrollable={false}>
{(tree, onChange) => (
<SchemaEditorWidget tree={tree} onChange={onChange} />
)}
</ViewPanel>
<ViewPanel type="MARKUP" scrollable={false}>
{(tree) => <MarkupSchemaWidget tree={tree} />}
</ViewPanel>
<ViewPanel type="PREVIEW">
{(tree) => <PreviewWidget tree={tree} />}
</ViewPanel>
</ViewportPanel>
</WorkspacePanel>
</Workspace>
<SettingsPanel title="panels.PropertySettings">
<SettingsForm uploadAction="https://www.mocky.io/v2/5cc8019d300000980a055e76" />
</SettingsPanel>
</StudioPanel>
</Designer>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
4、场景案例-登录注册
4.1、Markup Schema 案例
import React from 'react'
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import { Form, FormItem, Input, Password, Submit } from '@formily/antd'
import { Tabs, Card } from 'antd'
import * as ICONS from '@ant-design/icons'
import { VerifyCode } from './VerifyCode'
const normalForm = createForm({
validateFirst: true,
})
const phoneForm = createForm({
validateFirst: true,
})
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Password,
VerifyCode,
},
scope: {
icon(name) {
return React.createElement(ICONS[name])
},
},
})
export default () => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
background: '#eee',
padding: '40px 0',
}}
>
<Card style={{ width: 400 }}>
<Tabs style={{ overflow: 'visible', marginTop: -10 }}>
<Tabs.TabPane key="1" tab="账密登录">
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField>
<SchemaField.String
name="username"
title="用户名"
required
x-decorator="FormItem"
x-component="Input"
x-validator={{
required: true,
}}
x-component-props={{
prefix: "{{icon('UserOutlined')}}",
}}
/>
<SchemaField.String
name="password"
title="密码"
required
x-decorator="FormItem"
x-component="Password"
x-component-props={{
prefix: "{{icon('LockOutlined')}}",
}}
/>
</SchemaField>
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="手机登录">
<Form
form={phoneForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField>
<SchemaField.String
name="phone"
title="手机号"
required
x-validator="phone"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
prefix: "{{icon('PhoneOutlined')}}",
}}
/>
<SchemaField.String
name="verifyCode"
title="验证码"
required
x-decorator="FormItem"
x-component="VerifyCode"
x-component-props={{
prefix: "{{icon('LockOutlined')}}",
}}
x-reactions={[
{
dependencies: ['.phone#value', '.phone#valid'],
fulfill: {
state: {
'component[1].readyPost': '{{$deps[0] && $deps[1]}}',
'component[1].phoneNumber': '{{$deps[0]}}',
},
},
},
]}
/>
</SchemaField>
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
</Tabs>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<a href="#新用户注册">新用户注册</a>
<a href="#忘记密码">忘记密码?</a>
</div>
</Card>
</div>
)
}
4.2、JSON Schema 案例
import React from 'react'
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import { Form, FormItem, Input, Password, Submit } from '@formily/antd'
import { Tabs, Card } from 'antd'
import * as ICONS from '@ant-design/icons'
import { VerifyCode } from './VerifyCode'
const normalForm = createForm({
validateFirst: true,
})
const phoneForm = createForm({
validateFirst: true,
})
const SchemaField = createSchemaField({
components: {
FormItem,
Input,
Password,
VerifyCode,
},
scope: {
icon(name) {
return React.createElement(ICONS[name])
},
},
})
const normalSchema = {
type: 'object',
properties: {
username: {
type: 'string',
title: '用户名',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
prefix: "{{icon('UserOutlined')}}",
},
},
password: {
type: 'string',
title: '密码',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Password',
'x-component-props': {
prefix: "{{icon('LockOutlined')}}",
},
},
},
}
const phoneSchema = {
type: 'object',
properties: {
phone: {
type: 'string',
title: '手机号',
required: true,
'x-validator': 'phone',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
prefix: "{{icon('PhoneOutlined')}}",
},
},
verifyCode: {
type: 'string',
title: '验证码',
required: true,
'x-decorator': 'FormItem',
'x-component': 'VerifyCode',
'x-component-props': {
prefix: "{{icon('LockOutlined')}}",
},
'x-reactions': [
{
dependencies: ['.phone#value', '.phone#valid'],
fulfill: {
state: {
'component[1].readyPost': '{{$deps[0] && $deps[1]}}',
'component[1].phoneNumber': '{{$deps[0]}}',
},
},
},
],
},
},
}
export default () => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
background: '#eee',
padding: '40px 0',
}}
>
<Card style={{ width: 400 }}>
<Tabs style={{ overflow: 'visible', marginTop: -10 }}>
<Tabs.TabPane key="1" tab="账密登录">
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField schema={normalSchema} />
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="手机登录">
<Form
form={phoneForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<SchemaField schema={phoneSchema} />
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
</Tabs>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<a href="#新用户注册">新用户注册</a>
<a href="#忘记密码">忘记密码?</a>
</div>
</Card>
</div>
)
}
4.3、纯 JSX 案例
import React from 'react'
import { createForm } from '@formily/core'
import { Field } from '@formily/react'
import { Form, FormItem, Input, Password, Submit } from '@formily/antd'
import { Tabs, Card } from 'antd'
import { UserOutlined, LockOutlined, PhoneOutlined } from '@ant-design/icons'
import { VerifyCode } from './VerifyCode'
const normalForm = createForm({
validateFirst: true,
})
const phoneForm = createForm({
validateFirst: true,
})
export default () => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
background: '#eee',
padding: '40px 0',
}}
>
<Card style={{ width: 400 }}>
<Tabs style={{ overflow: 'visible', marginTop: -10 }}>
<Tabs.TabPane key="1" tab="账密登录">
<Form
form={normalForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<Field
name="username"
title="用户名"
required
decorator={[FormItem]}
component={[
Input,
{
prefix: <UserOutlined />,
},
]}
/>
<Field
name="password"
title="密码"
required
decorator={[FormItem]}
component={[
Password,
{
prefix: <LockOutlined />,
},
]}
/>
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
<Tabs.TabPane key="2" tab="手机登录">
<Form
form={phoneForm}
layout="vertical"
size="large"
onAutoSubmit={console.log}
>
<Field
name="phone"
title="手机号"
required
validator="phone"
decorator={[FormItem]}
component={[
Input,
{
prefix: <PhoneOutlined />,
},
]}
/>
<Field
name="verifyCode"
title="验证码"
required
reactions={(field) => {
const phone = field.query('.phone')
field.setComponentProps({
readyPost: phone.get('valid') && phone.get('value'),
phoneNumber: phone.get('value'),
})
}}
decorator={[FormItem]}
component={[
VerifyCode,
{
prefix: <LockOutlined />,
},
]}
/>
<Submit block size="large">
登录
</Submit>
</Form>
</Tabs.TabPane>
</Tabs>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<a href="#新用户注册">新用户注册</a>
<a href="#忘记密码">忘记密码?</a>
</div>
</Card>
</div>
)
}
没有谁能击垮你,除非你自甘堕落。