前言

学习笔记,仅供学习参考

主要功能点

  1. 可以输入数据,数据动态更新;
  2. 点击 submit,进行数据校验(仅做必填校验);
  3. 校验失败展示提示,控制台打印 Failed 详情信息;
  4. 校验成功控制台打印 Success 详情信息,并输出 form 表单数据;

注: 仅实现核心思想,更多业务细节忽略

学习目的

  1. 学习 antd4.x 对 form 表单的优化处理,控制 dom 的精准更新以提高性能;
  2. 学习 antd form 表单封装思路;

antd4.x form 表单优化点

  1. antd4.x 之前,form 的数据都做在 Form 的 state 中,一旦 form 表单某一项数据更新,整个 form 表单会全部更新,一旦表单逻辑复杂了,性能成本可想而知;
  2. antd4.x 通过新建一个类,对 form 表单的数据进行缓存和处理,一旦数据更新,通过forceUpdate方法对表单数据更改项精准更新;
  3. 兼容了 class 组件和函数组件;

上代码

代码目录结构

使用

函数组件使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React, { useEffect } from 'react'
import Form, { Field } from '@comps/Form'
import Input from '@comps/Form/Input'

const nameRules = [{ required: true, message: '请输入姓名!' }]
const passworRules = [{ required: true, message: '请输入密码!' }]

export default function MyForm() {
const [form] = Form.useForm()

useEffect(() => {
// 设置默认值
form.setFieldsValue({ username: 'default' })
}, [])

const onFinish = (values) => {
console.log('Success:', values)
}

const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo)
}

return (
<div className="my-form">
<h2>MyFrom</h2>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
<Field label="Username" name="username" rules={nameRules}>
<Input />
</Field>
<Field label="Password" name="password" rules={passworRules}>
<Input />
</Field>
<Field>
<button> submit </button>
</Field>
</Form>
</div>
)
}

class 组件使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useEffect } from 'react'
import Form, { Field } from '@comps/Form'
import Input from '@comps/Form/Input'

const nameRules = [{ required: true, message: '请输入姓名!' }]
const passworRules = [{ required: true, message: '请输入密码!' }]

// class 组件
export default class MyForm extends React.Component {
onFinish = (val) => {
console.log('onFinish', val) //sy-log
}

// 表单校验失败执行
onFinishFailed = (val) => {
console.log('onFinishFailed', val) //sy-log
}

render() {
return (
<div>
<h3>MyRCFieldForm</h3>
<Form onFinish={this.onFinish} onFinishFailed={this.onFinishFailed}>
<Field name="username" rules={[nameRules]}>
<Input placeholder="Username" />
</Field>
<Field name="password" rules={[passworRules]}>
<Input placeholder="Password" />
</Field>
<button>Submit</button>
</Form>
</div>
)
}
}

index

1
2
3
4
5
6
7
8
9
10
11
12
// import React from 'react'
import _Form from './Form'
import Field from './Field'
import useForm from './useForm'
import './Form.css'

const Form = _Form
Form.Field = Field
Form.useForm = useForm

export { Field, useForm }
export default Form

Form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React from 'react'
import FieldContext from './FeildContext'
import useForm from './useForm'

export default function Form(
{ form, children, onFinish, onFinishFailed },
ref
) {
// store 存储数据的
const [formInstance] = useForm(form)

// 子给父的ref传数据 新方法
// 将 formInstance 传给 Form 组件的使用者 ,然后再通过
// React.useImperativeHandle(ref, () => formInstance)

// 缓存 方法
formInstance.setCallback({
onFinish,
onFinishFailed,
})

return (
<form
className="ll-form"
onSubmit={(e) => {
e.preventDefault()
formInstance.submit()
}}
>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
)
}

Field

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import React from 'react'
import FieldContext from './FeildContext'

export default class Field extends React.Component {
static contextType = FieldContext

constructor(props) {
super(props)
}

componentDidMount() {
// 将field实例存到formstore中,后续formStore中的数据更新了,通过调用实例的onStoreChange触发组件的重新渲染
const { setFieldEntities } = this.context

// setFieldEntities 函数返回一个销毁存储的方法
this.unregister = setFieldEntities(this)
}

// ! 组件销毁同时删除formstore中对field的缓存
componentWillUnmount() {
if (this.unregister) {
this.unregister()
}
}

// store 中的数据更新之后要手动刷新页面
onStoreChange = () => {
this.forceUpdate()
}

getControlled = () => {
const { name } = this.props
const { setFieldsValue, getFieldValue } = this.context

return {
value: getFieldValue(name),
onChange: (e) => {
const val = e.target.value
// store set 根据 name的唯一性
setFieldsValue({ [name]: val })
},
}
}

render() {
const { children, label, name } = this.props
const { getVaildateErrByName } = this.context

const err = getVaildateErrByName(name)

// 给子组件增加属性、方法
const returnChildrenNode = React.cloneElement(
children,
this.getControlled()
)

return (
<div
className={`ll-form-field ${
err && err.name === name ? 'll-form-field-err' : ''
}`}
>
{label && (
<div className="ll-form-label ll-form-label-left">
<label> {label}</label>
</div>
)}
<div className="ll-form-wrap">
{returnChildrenNode}
{err && err.name === name && (
<div className="ll-form-field-explain ll-form-field-error">
<div role="alert">{err.message}</div>
</div>
)}
</div>
</div>
)
}
}

FeildContext

1
2
3
4
5
6
7
import React from 'react'

// 创建context

const FieldContext = React.createContext()

export default FieldContext

useForm

form 表单的数据全部在这个类里面处理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import React from 'react'

// store 状态管理库
// 开辟一块空间,存储form中的数据,空间换性能
class FormStore {
constructor() {
// 存状态
this.store = {}
// 存fields form 下面的子组件
this.fieldEntities = {}
// 存储cbs form 身上的事件 例如onFinish等
this.callbacks = {}
// 缓存校验错误数据
this.err = {}
}

// get 单个值
getFieldValue = (name) => {
return this.store[name]
}

// get 所有store数据
getFieldsValue = () => {
return { ...this.store }
}

// set store
setFieldsValue = (newStore) => {
this.store = {
...this.store,
...newStore,
}

// 更新视图需要准确的拿到对应的 数据变化的filed实例 进行更新
const keys = Object.keys(newStore)

keys.length &&
keys.forEach((name) => {
this.onStoreChange(name)
})
}

// 存 filed
// 订阅和取消订阅,监听和取消监听要成对出现
setFieldEntities = (entity) => {
// 订阅
entity.props.name && (this.fieldEntities[entity.props.name] = entity)

// 返回一个卸载方法 取消订阅
return () => {
// 删除field 实例
delete this.fieldEntities[entity.props.name]
// 删除store存的对应的数据
delete this.store[entity.props.name]
}
}

setCallback = (newCallbacks) => {
this.callbacks = {
...this.callbacks,
...newCallbacks,
}
}

validate = () => {
let err = []

for (let name in this.fieldEntities) {
let rules = this.fieldEntities[name].props.rules || null
rules = rules && rules[0]

if (!rules) continue

const value = this.getFieldValue(name)

if (!(rules.required && value)) {
const o = {
message: rules.message,
value,
name: name,
}
err.push(o)
// 缓存校验出来的err
this.err[name] = o
this.onStoreChange(name)
} else {
if (this.err[name]) {
// 校验成功的 清除记录 并刷新对应的 field
delete this.err[name]
this.onStoreChange(name)
}
}
}

return err
}

getVaildateErrByName = (name) => {
return this.err[name] || null
}

submit = () => {
const err = this.validate()
const { onFinish, onFinishFailed } = this.callbacks
if (err.length > 0) {
// 校验出错
onFinishFailed(err, this.getFieldsValue())
} else {
// 校验通过
onFinish(this.getFieldsValue())
}
}

onStoreChange = (name) => {
this.fieldEntities[name] && this.fieldEntities[name].onStoreChange()
}

getForm = () => {
return {
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
setFieldEntities: this.setFieldEntities,
getVaildateErrByName: this.getVaildateErrByName,
setCallback: this.setCallback,
submit: this.submit,
}
}
}

// 储存状态管理库
export default function useForm(form) {
// 实例 全局只会生成一次,不能每次刷新一次 初始化一次
// React.useRef() 拥有 跨渲染周期 保存数据的 能力
const formRef = React.useRef()

if (!formRef.current) {
if (form) {
formRef.current = form
} else {
// 通过getForm 获取formstore的方法
const formStore = new FormStore()
formRef.current = formStore.getForm()
}
}

return [formRef.current]
}

Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react'

// 受控组件,属性、方法均控制在父级
const Input = (props) => {
return <input className="ll-input" {...props} />
}

const CustomizeInput = ({ value = '', ...props }) => (
<div className="ll-form-input">
<Input value={value} {...props} />
</div>
)

export default CustomizeInput