mobx是一个简单可扩展的状态管理库
mobx学习成本更低,性能更好的的状态解决方案
状态变化引起的副作用应该被自动触发
observable
observable
就是一种让数据的变化可以被观察的方法import { observable } from 'mobx';
const p1 = observable([1, 2, 3]);
p1.push(4);
p1.pop();
console.log(p1);
console.log(Array.isArray(p1));
类型 | 描述 |
---|---|
String | 字符串 |
Boolean | 布尔值 |
Number | 数字 |
Symbol | 独一无二的值 |
import { observable } from 'mobx';
let num = observable.box(10);
let str = observable.box('hello');
let bool = observable.box(true);
console.log(num.get(), str.get(), bool.get());
num.set(100);
str.set('world');
bool.set(false);
console.log(num.get(), str.get(), bool.get());
react
和vue
等框架antd
和element
等组件库npm init vite@latest
npm install @formily/reactive @formily/core @formily/reactive-react @formily/react @formily/antd ajv less --save
less`
文件中引入 antd
的 less
文件会有一个~
前置符,这种写法对于 ESM 构建工具是不兼容的javascriptEnabled
这个参数在less3.0之后是默认为falsevite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
+ plugins: [react({
+ jsxRuntime: 'classic'
+ })],
+ resolve: {
+ alias: [
+ { find: /^~/, replacement: '' }
+ ]
+ },
+ css: {
+ preprocessorOptions: {
+ less: {
+ // 支持内联 JavaScript
+ javascriptEnabled: true,
+ }
+ }
+ }
})
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
+ "strict": false,
+ "noImplicitAny": false,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
View
和 ViewModel
两层能力,View
则是@formily/react
,专门用来与@formily/core
做桥接通讯的,所以,@formily/core
的定位就是 ViewModel
层<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MVVM</title>
</head>
<body>
<input id="bookTitle" value="红楼梦"/>
<script>
class Book{
constructor(title){
this.title=title;
}
}
//Model
let book = new Book('红楼梦');
//ViewModel
let viewModel = {display:'block'};
Object.defineProperty(viewModel,'title',{
get(){
return book.title;
},
set(newTitle){
bookTitle.value = book.title = newTitle;
}
});
Object.defineProperty(viewModel,'display',{
get(){
return bookTitle.style.display;
},
set(display){
bookTitle.style.display = display;
}
});
//View(视图层)负责维护 UI 结构与样式
//同时负责与 ViewModel(视图模型)做数据绑定
//这里的数据绑定关系是双向的,也就是,ViewModel(视图模型)的数据发生变化,会触发 View(视图层)的更新
viewModel.title='新红楼梦';
setTimeout(()=>{
viewModel.display= 'none';
},3000);
//同时视图层的数据变化又会触发 ViewModel(视图模型)的变化
bookTitle.onchange = (event)=>{
viewModel.title = event.target.value;
}
</script>
</body>
</html>
observable
对象observable
对象,字面意思是可订阅对象,我们通过创建一个可订阅对象,在每次操作该对象的属性数据的过程中,会自动通知订阅者ES Proxy
来创建的,它可以做到完美劫持数据操作tracker
函数,这个函数在执行的时候,如果函数内部有对 observable
对象中的某个属性进行读操作会进行依赖收集,那当前 reaction
就会与该属性进行一个绑定(依赖追踪),该属性在其它地方发生了写操作,就会触发 tracker
函数重复执行tracker
函数执行的时候都会重新收集依赖,依赖变化时又会重新触发tracker
执行tracker
函数,如果函数内部有消费 observable
数据,数据发生变化时,tracker
函数会重复执行src\main.tsx
import { observable, autorun } from './@formily/reactive'
const obs = observable({
name: 'zhu',
})
const tracker = () => {
console.log(obs.name);
}
autorun(tracker)
obs.name = 'feng';
/**
zhu
feng
*/
import { observable, autorun } from '@formily/reactive'
const obs = observable({
name: 'zhu',
+ age: 12
})
+let counter = 0;
const tracker = () => {
console.log(obs.name);
+ if (counter++) {
+ console.log(obs.age);
+ }
}
autorun(tracker)
+obs.age = 13;
obs.name = 'feng';
+obs.age = 14;
/**
tracker第1次执行
zhu
tracker第2次执行
feng
13
tracker第3次执行
feng
14
*/
src\@formily\reactive\index.ts
const RawReactionsMap = new WeakMap()
let currentReaction;
export function observable(value) {
return new Proxy(value, baseHandlers)
}
export const autorun = (tracker) => {
const reaction = () => {
currentReaction = reaction;
tracker()
currentReaction = null;
}
reaction()
}
const baseHandlers = {
get(target, key) {
const result = target[key]
if (currentReaction) {
addRawReactionsMap(target, key, currentReaction)
}
return result
},
set(target, key, value) {
target[key] = value
RawReactionsMap.get(target)?.get(key)?.forEach((reaction) => reaction())
return true;
}
}
const addRawReactionsMap = (target, key, reaction) => {
const reactionsMap = RawReactionsMap.get(target)
if (reactionsMap) {
const reactions = reactionsMap.get(key)
if (reactions) {
reactions.push(reaction)
} else {
reactionsMap.set(key, [reaction])
}
return reactionsMap
} else {
const reactionsMap = new Map()
reactionsMap.set(key, [reaction]);
RawReactionsMap.set(target, reactionsMap)
return reactionsMap
}
}
Function RenderProps
,只要在 Function
内部消费到的任何响应式数据,都会随数据变化而自动重新渲染,也更容易实现局部精确渲染src\main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App';
ReactDOM.render(<App />, document.getElementById('root')!);
src\App.tsx
import { observable } from './@formily/reactive'
import { Observer } from './@formily/reactive-react'
const username = observable({ value: 'zhangsan' })
const age = observable({ value: 14 })
export default () => {
return (
<>
<Observer>
{() => (
<input
value={username.value}
onChange={(event) => {
username.value = event.target.value
}}
/>
)}
</Observer>
<Observer>{() => {
console.log('username render');
return <div>{username.value}</div>;
}}</Observer>
<Observer>
{() => (
<input
value={age.value}
onChange={(event) => {
age.value = +event.target.value
}}
/>
)}
</Observer>
<Observer>{() => {
console.log('age render');
return <div>{age.value}</div>;
}}</Observer>
</>
)
}
src\@formily\reactive-react\index.tsx
import React, { useReducer } from 'react';
import { Tracker } from '../../@formily/reactive'
export const Observer = (props) => {
const [, forceUpdate] = useReducer(x => x + 1, 0)
const trackerRef = React.useRef(null)
if (!trackerRef.current)
trackerRef.current = new Tracker(forceUpdate)
return trackerRef.current.track(props.children)
}
src\@formily\reactive\index.ts
const RawReactionsMap = new WeakMap()
let currentReaction;
export function observable(value) {
return new Proxy(value, baseHandlers)
}
export const autorun = (tracker) => {
const reaction = () => {
currentReaction = reaction;
tracker()
currentReaction = null;
}
reaction()
}
const baseHandlers = {
get(target, key) {
const result = target[key]
if (currentReaction) {
addRawReactionsMap(target, key, currentReaction)
}
return result
},
set(target, key, value) {
target[key] = value
RawReactionsMap.get(target)?.get(key)?.forEach((reaction) => {
+ if (typeof reaction._scheduler === 'function') {
+ reaction._scheduler()
+ } else {
+ reaction()
+ }
})
return true;
}
}
const addRawReactionsMap = (target, key, reaction) => {
const reactionsMap = RawReactionsMap.get(target)
if (reactionsMap) {
const reactions = reactionsMap.get(key)
if (reactions) {
reactions.push(reaction)
} else {
reactionsMap.set(key, [reaction])
}
return reactionsMap
} else {
const reactionsMap = new Map()
reactionsMap.set(key, [reaction]);
RawReactionsMap.set(target, reactionsMap)
return reactionsMap
}
}
+export class Tracker {
+ constructor(scheduler) {
+ this.track._scheduler = scheduler
+ }
+ track: any = (tracker) => {
+ currentReaction = this.track;
+ return tracker()
+ }
+}
领域模型
createForm
所返回的核心表单模型createField
所返回的字段模型ViewModel
src\main.tsx
import { createForm } from '@formily/core'
const form = createForm()
const field = form.createField({ name: 'target' })
interface Form {
values,//值
visible, //是否可见
submit() //提交
}
interface Field {
value, //值
visible, //是否可见
setValue() //设置值
}
FormPath
在 Formily 中核心是解决路径匹配问题和数据操作问题src\main.tsx
import { FormPath } from '@formily/core'
const target = { array: [] }
//点路径 就是我们最常用的a.b.c格式,用点符号来分割每个路径节点,主要用来读写数据
FormPath.setIn(target, 'a.b.c', 'dotValue')
console.log(FormPath.getIn(target, 'a.b.c')) //'dotValue'
//下标路径 对于数组路径,都会有下标,我们的下标可以用点语法,也可以用中括号
FormPath.setIn(target, 'array.0.d', 'arrayValue')
console.log(FormPath.getIn(target, 'array.0.d')) //arrayValue
//解构表达式 解构表达式类似于 ES6 的解构语法,在前后端数据不一致的场景非常适用,解构表达式会作为点路径的某个节点
FormPath.setIn(target, 'parent.[f,g]', [1, 2])
console.log(JSON.stringify(target))
//{"array":[{"d":"arrayValue"}],"a":{"b":{"c":"dotValue"}},"parent":{"f":1,"g":2}}
import { useMemo, useState } from 'react'
import { createForm, onFormInit, onFormReact } from '@formily/core'
export default () => {
const [state, setState] = useState('init')
const form = useMemo(
() =>
createForm({
effects() {
onFormInit(() => {
setState('表单已初始化')
})
onFormReact((form) => {
if (form.values.input == 'Hello') {
setState('响应Hello')
} else if (form.values.input == 'World') {
setState('响应World')
}
})
},
}),
[]
)
return (
<div>
<p>{state}</p>
<button
onClick={() => {
form.setValuesIn('input', 'Hello')
}}
>
Hello
</button>
<button
onClick={() => {
form.setValuesIn('input', 'World')
}}
>
World
</button>
</div>
)
}
import Ajv from 'ajv';
const ajv = new Ajv()
const schema = {
type: "object",
properties: {
foo: { type: "integer" },
bar: { type: "string" }
},
required: ["foo"],
additionalProperties: false
}
const validate = ajv.compile(schema)
const data = {
foo: 1,
bar: "abc",
age: 1
}
const valid = validate(data)
if (!valid)
console.log(validate.errors)
Formily
扩展了JSON-Schema
属性,统一以x-*
格式来表达扩展属性以描述数据无关的布局容器和控件,实现UI协议与数据协议混合在一起void
,代表一个虚数据节点,表示该节点并不占用实际数据结构{
"type": "string",
"title": "字符串",
"description": "这是一个字符串",
"x-component": "Input",//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
}
}
{
"type": "void",
"title": "卡片",
"description": "这是一个卡片",
"x-component": "Card",//字段 UI 组件属性
"properties": {
"name": {
"type": "string",
"title": "字符串",
"description": "这是一个字符串",
"x-component": "Input",//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
}
}
}
}
@formily/core
的 createField
React 实现,它是专门用于将 ViewModel
与输入控件做绑定的桥接组件
SchemaField
组件是专门用于解析JSON-Schema
动态渲染表单的组件。 在使用SchemaField
组件的时候,需要通过 createSchemaField 工厂函数创建一个 SchemaField`
组件@formily/react
协议驱动最核心的部分json-schema
的能力json-schema
转换成 Field Model
的能力json-schema
表达式的能力x-component
的组件标识与createSchemaField
传入的组件集合的 Key
匹配x-decorator
的组件标识与createSchemaField
传入的组件集合的 Key
匹配Schema
的每个属性都能使用字符串表达式{{expression}}
,表达式变量可以从 createSchemaField
中传入,也可以从 SchemaField
组件中传入x-reactions
中的表达式消费,与 x-reactions`
定义的 dependencies`
对应,数组顺序一致JSX
场景校验属性,使用 validator
属性实现校验Markup(JSON) Schema
场景协议校验属性校验,使用 JSON Schema
本身的校验属性与 x-validator
属性实现校验src\App.tsx
import { createForm } from '@formily/core'
import { Field } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input, NumberPicker } from '@formily/antd'
//createForm创建一个 Form 实例,作为 ViewModel 给 UI 框架层消费
const form = createForm()
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<Field
name="name"
title="姓名"
required
component={[Input]}
decorator={[FormItem]}
/>
<Field
name="age"
title="年龄"
validator={{ maximum: 5 }}
component={[NumberPicker]}
decorator={[FormItem]}
/>
</Form>
)
}
export default App;
@formily/react
协议驱动最核心的部分json-schema
的能力json-schema
转换成 Field Model
的能力json-schema
表达式的能力src\App.tsx
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input } from '@formily/antd'
const form = createForm()
const SchemaField = createSchemaField({
components: {
FormItem,
Input
},
})
const schema = {
type: 'object',
properties: {
name: {
title: `姓名`,
type: 'string',
required: true,
'x-decorator': 'FormItem',//字段 UI 包装器组件
'x-component': 'Input',//字段 UI 组件属性
},
age: {
title: `邮箱`,
type: 'string',
required: true,
'x-validator': 'email',
'x-decorator': 'FormItem',//字段 UI 包装器组件
'x-component': 'Input',//字段 UI 组件属性
},
},
}
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<SchemaField schema={schema} />
</Form>
)
}
export default App;
src\App.tsx
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input, NumberPicker } from '@formily/antd'
const form = createForm()
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
NumberPicker
},
})
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<SchemaField>
<SchemaField.String
name="name"
title="姓名"
required
x-component="Input"//字段 UI 组件属性
x-decorator="FormItem"//字段 UI 包装器组件
/>
<SchemaField.Number
name="age"
title="年龄"
maximum={120}
x-component="NumberPicker"//字段 UI 组件属性
x-decorator="FormItem"//字段 UI 包装器组件
/>
</SchemaField>
</Form>
)
}
export default App;
effects
或者 x-reactions
中实现联动校验reaction
对象里包含target
,则代表主动
联动模式,否则代表被动
联动模式src\App.tsx
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input } from '@formily/antd'
const form = createForm()
const SchemaField = createSchemaField({
components: {
FormItem,
Input
},
})
const schema = {
type: 'object',
properties: {
source: {
title: `来源`,
type: 'string',
required: true,
'x-decorator': 'FormItem',//字段 UI 包装器组件
'x-component': 'Input',//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
},
"x-reactions": [//字段联动协议
{
"target": "target",///要操作的字段路径,支持FormPathPattern路径语法
//代表当前字段实例,可以在普通属性表达式中使用,也能在 x-reactions 中使用
"when": "{{$self.value == '123'}}",//联动条件
"fulfill": { //满足条件
"state": {//更新状态
"visible": true
}
},
"otherwise": { //不满足条件
"state": {//更新状态
"visible": false
}
}
}
]
},
target: {
"title": "目标",
"type": "string",
"x-component": "Input",//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
},
'x-decorator': 'FormItem'//字段 UI 包装器组件
}
},
}
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<SchemaField schema={schema} />
</Form>
)
}
export default App;
src\App.tsx
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input } from '@formily/antd'
const form = createForm()
const SchemaField = createSchemaField({
components: {
FormItem,
Input
},
})
const schema = {
type: 'object',
properties: {
source: {
title: `来源`,
type: 'string',
required: true,
'x-decorator': 'FormItem',//字段 UI 包装器组件
'x-component': 'Input',//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
}
},
target: {
"title": "目标",
"type": "string",
"x-component": "Input",//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
},
'x-decorator': 'FormItem',//字段 UI 包装器组件
"x-reactions": [//字段联动协议
{
"dependencies": ["source"],
//只能在x-reactions中的表达式消费,与 x-reactions 定义的 dependencies 对应,数组顺序一致
"when": "{{$deps[0] == '123'}}",
"fulfill": {
"state": {
"visible": true
}
},
"otherwise": {
"state": {
"visible": false
}
}
}
]
}
},
}
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<SchemaField schema={schema} />
</Form>
)
}
export default App;
src\App.tsx
import { createForm, onFieldMount, onFieldValueChange } from '@formily/core'
import { createSchemaField } from '@formily/react'
import 'antd/dist/antd.css'
import { Form, FormItem, Input } from '@formily/antd'
const form = createForm({
effects() {//effects 副作用逻辑,用于实现各种联动逻辑
//用于监听某个字段已挂载的副作用钩子
onFieldMount('target', (field: any) => {
//可以设置字段状态
form.setFieldState(field.query('target'), (state) => {
if (field.value === '123') {
state.visible = true;
} else {
state.visible = false;
}
})
})
//用于监听某个字段值变化的副作用钩子
onFieldValueChange('source', (field: any) => {
form.setFieldState(field.query('target'), (state) => {
if (field.value === '123') {
state.visible = true;
} else {
state.visible = false;
}
})
})
},
})
const SchemaField = createSchemaField({
components: {
FormItem,
Input
},
})
const schema = {
type: 'object',
properties: {
source: {
title: `来源`,
type: 'string',
required: true,
'x-decorator': 'FormItem',//字段 UI 包装器组件
'x-component': 'Input',//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
}
},
target: {
"title": "目标",
"type": "string",
'x-decorator': 'FormItem',//字段 UI 包装器组件
"x-component": "Input",//字段 UI 组件属性
"x-component-props": {//字段 UI 组件属性
"placeholder": "请输入"
}
}
}
}
function App() {
return (
<Form form={form} labelCol={6} wrapperCol={10}>
<SchemaField schema={schema} />
</Form>
)
}
export default App;
import React from 'react'
import { createForm } from '@formily/core'
import { createSchemaField } from '@formily/react'
import {
Form,
FormItem,
FormLayout,
Input,
Select,
Password,
Cascader,
DatePicker,
Submit,
Space,
FormGrid,
Upload,
ArrayItems,
Editable,
FormButtonGroup,
} from '@formily/antd'
import { action } from '@formily/reactive'
import { Card, Button } from 'antd'
import { UploadOutlined } from '@ant-design/icons'
const form = createForm({
validateFirst: true,
})
const IDUpload = (props) => {
return (
<Upload
{...props}
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
headers={{
authorization: 'authorization-text',
}}
>
<Button icon={<UploadOutlined />}>上传复印件</Button>
</Upload>
)
}
const SchemaField = createSchemaField({
components: {
FormItem,
FormGrid,
FormLayout,
Input,
DatePicker,
Cascader,
Select,
Password,
IDUpload,
Space,
ArrayItems,
Editable,
},
scope: {
fetchAddress: (field) => {
const transform = (data = {}) => {
return Object.entries(data).reduce((buf, [key, value]) => {
if (typeof value === 'string')
return buf.concat({
label: value,
value: key,
})
const { name, code, cities, districts } = value
const _cities = transform(cities)
const _districts = transform(districts)
return buf.concat({
label: name,
value: code,
children: _cities.length
? _cities
: _districts.length
? _districts
: undefined,
})
}, [])
}
field.loading = true
fetch('//unpkg.com/china-location/dist/location.json')
.then((res) => res.json())
.then(
action.bound((data) => {
field.dataSource = transform(data)
field.loading = false
})
)
},
},
})
export default () => {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
background: '#eee',
padding: '40px 0',
}}
>
<Card title="新用户注册" style={{ width: 620 }}>
<Form
form={form}
labelCol={5}
wrapperCol={16}
onAutoSubmit={console.log}
>
<SchemaField>
<SchemaField.String
name="username"
title="用户名"
required
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="password"
title="密码"
required
x-decorator="FormItem"
x-component="Password"
x-component-props={{
checkStrength: true,
}}
x-reactions={[
{
dependencies: ['.confirm_password'],
fulfill: {
state: {
selfErrors:
'{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
},
},
},
]}
/>
<SchemaField.String
name="confirm_password"
title="确认密码"
required
x-decorator="FormItem"
x-component="Password"
x-component-props={{
checkStrength: true,
}}
x-reactions={[
{
dependencies: ['.password'],
fulfill: {
state: {
selfErrors:
'{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
},
},
},
]}
/>
<SchemaField.Void
title="姓名"
x-decorator="FormItem"
x-decorator-props={{
asterisk: true,
feedbackLayout: 'none',
}}
x-component="FormGrid"
>
<SchemaField.String
name="firstName"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
placeholder: '姓',
}}
required
/>
<SchemaField.String
name="lastName"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
placeholder: '名',
}}
required
/>
</SchemaField.Void>
<SchemaField.String
name="email"
title="邮箱"
required
x-validator="email"
x-decorator="FormItem"
x-component="Input"
/>
<SchemaField.String
name="gender"
title="性别"
x-decorator="FormItem"
x-component="Select"
enum={[
{
label: '男',
value: 1,
},
{
label: '女',
value: 2,
},
{
label: '第三性别',
value: 3,
},
]}
required
/>
<SchemaField.String
name="birthday"
title="生日"
required
x-decorator="FormItem"
x-component="DatePicker"
/>
<SchemaField.String
name="address"
title="地址"
required
x-decorator="FormItem"
x-component="Cascader"
x-reactions="{{fetchAddress}}"
/>
<SchemaField.String
name="idCard"
title="身份证复印件"
required
x-decorator="FormItem"
x-component="IDUpload"
/>
<SchemaField.Array
name="contacts"
title="联系人信息"
required
x-decorator="FormItem"
x-component="ArrayItems"
>
<SchemaField.Object x-component="ArrayItems.Item">
<SchemaField.Void
x-decorator="FormItem"
x-component="ArrayItems.SortHandle"
/>
<SchemaField.Void
name="popover"
title="维护联系人信息"
x-decorator="Editable.Popover"
x-component="FormLayout"
x-component-props={{
layout: 'vertical',
}}
x-reactions={[
{
dependencies: ['.popover.name'],
fulfill: {
schema: {
title: '{{$deps[0]}}',
},
},
},
]}
>
<SchemaField.String
name="name"
required
title="姓名"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
style: {
width: 300,
},
}}
/>
<SchemaField.String
name="email"
title="邮箱"
x-validator={[{ required: true }, 'email']}
x-decorator="FormItem"
x-component="Input"
x-component-props={{
style: {
width: 300,
},
}}
/>
<SchemaField.String
name="phone"
required
title="手机号"
x-validator="phone"
x-decorator="FormItem"
x-component="Input"
x-component-props={{
style: {
width: 300,
},
}}
/>
</SchemaField.Void>
<SchemaField.Void
x-decorator="FormItem"
x-component="ArrayItems.Remove"
/>
</SchemaField.Object>
<SchemaField.Void
x-component="ArrayItems.Addition"
title="新增联系人"
/>
</SchemaField.Array>
</SchemaField>
<FormButtonGroup.FormItem>
<Submit block size="large">
注册
</Submit>
</FormButtonGroup.FormItem>
</Form>
</Card>
</div>
)
}
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="Input"
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>
)
}
import React, { useState } from 'react'
import { Input, Button } from 'antd'
interface IVerifyCodeProps {
value?: any
onChange?: (value: any) => void
readyPost?: boolean
phoneNumber?: number
style?: React.CSSProperties
}
export const VerifyCode: React.FC<React.PropsWithChildren<IVerifyCodeProps>> =
({ value, onChange, readyPost, phoneNumber, ...props }) => {
const [lastTime, setLastTime] = useState(0)
const counting = (time = 20) => {
if (time < 0) return
setLastTime(time)
setTimeout(() => {
counting(time - 1)
}, 1000)
}
return (
<div
style={{ display: 'inline-flex', width: '100%', alignItems: 'center' }}
>
<Input
{...props}
style={{ marginRight: 5, ...props.style }}
value={value}
onChange={onChange}
/>
<div
style={{
flexShrink: 0,
color: '#999',
width: 100,
height: 35,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{lastTime === 0 && (
<Button
disabled={!readyPost}
block
onClick={() => {
if (phoneNumber) {
console.log(`post code by phone number ${phoneNumber}`)
}
counting()
}}
>
发送验证码
</Button>
)}
{lastTime > 0 && <span>剩余{lastTime}秒</span>}
</div>
</div>
)
}