React表单实现

受控表单

状态变化交由React处理

1
2
3
4
5
6
7
8
9
10
11
12
13
const App = () => {
const [value, setValue] = useState(0)
const handleChange = (e) => { setValue(e.target.value) }

return (
<form onSubmit={() => {
alert(value);
}}>
<input type="text" onChange={handleChange} />
<button type="submit">submit</button>
</form>
)
};

如上述代码,input框监听onChange方法,每当数据改变时调用setValue进行数据更新。

非受控表单

状态变化通过ref引用获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const App = () => {
const input = useRef(null)

return (
<form onSubmit={() => {
const data = input.current.value
alert(data);
}}>
<input type="text" ref={input} />
<br />
<button type="submit">submit</button>
</form>
)
};

输入框的值不通过setState更新,而是从ref中获取。

实现表单组件

了解使用方式

以ArcoDesign为例

可以在**Form.Item传入 field**属性,即可使控件变为受控组件,表单项的值都将会被 Form 收集。

  1. 受控模式下 FormItem会接管控件,自动给表单控件添加相应的 valueonChange,所有的数据收集都由 Form 内部完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Form() {
return (
<Form
onSubmit={(v) => {
console.log(v);
}}
style={{ width: 600 }}
>
<FormItem label='Username' field='name'>
<Input />
</FormItem>
<FormItem label='Age' field='age'>
<InputNumber />
</FormItem>
<FormItem>
<Button type='primary' htmlType='submit'>
Submit
</Button>
</FormItem>
</Form>
);
}

实现

useForm

  1. 通过 useForm() 获取 formInstance 实例,form表单的数据也在实例上
  2. formInstance 实例对外提供了全局的方法如 setFieldsValuegetFieldsValue
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
import React, { useRef } from "react";

class FormStore {
// 存储表单数据
store = {}
// 获取单个字段值
getFieldValue = (name) => {
return this.store[name]
}
// 获取所有字段值
getFieldsValue = () => {
return this.store
}
// 设置字段的值
setFieldsValue = (newStore) => {
// 更新store的值
this.store = {
...this.store,
...newStore,
}
}
// 提供FormStore实例方法
getForm = () => ({
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
});
}
// 创建单例formStore
export default function useForm(form) {
const formRef = useRef();
if (!formRef.current) {
if (form) {
formRef.current = form;
} else {
const formStore = new FormStore();
formRef.current = formStore.getForm();
}
}
return [formRef.current]
}

Form

  1. formInstance实例进行传递
  2. 然后渲染子节点

创建公用的Context

1
const FieldContext = React.createContext({});

阻止onSubmit默认事件,并执行Form的onSubmit方法,传入最新的表单数据,将formInstance实例传递下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Form = (props) => {
const { form, onSubmit, children, ...restProps } = props;
const [formInstance] = useForm(form) as any;
return (
<form
{...restProps}
onSubmit={(e) => {
e.preventDefault()
onSubmit(formInstance.getFieldsValue())
}}>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
)
}

FormItem

  1. 调用cloneElement给子组件添加valueonChange的props
  2. onChange时调用setFieldsValue更新表单数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Item = (props) => {
const context = useContext(FieldContext);
const { children } = props;
// Field 中传进来的子元素变为受控组件,也就是主动添加上 value 和 onChange 属性方法
const getControlled = () => {
const { field } = props;
const { getFieldValue, setFieldsValue } = context
return {
value: getFieldValue(field),
onChange: (event: any) => {
const newValue = event.target.value
setFieldsValue({ [field]: newValue })
},
}
}

return cloneElement(children, getControlled())
}

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import useForm from './useForm.js';
import React from 'react';
import './App.css';
import { Form, Item } from './Form.tsx';
const App = () => {
const [form] = useForm();
return (
<Form
form={form}
onSubmit={(v) => {
console.log(v);
}}
>
<Item field="usename">
<input />
</Item>
<Item>
<button type="submit">submit</button>
</Item>
</Form>
)
}

总结

整体来看,使用useForm创建实例,通过这个实例存储表单数据,通过给子组件注入props实现收集数据的功能。这里仅做了一个简单实现,还有许多特性有待拓展。


React表单实现
https://jing-jiu.github.io/jing-jiu/2022/03/05/Framework/React/表单组件/
作者
Jing-Jiu
发布于
2022年3月5日
许可协议