受控表单
状态变化交由React处理
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引用获取
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 收集。
- 受控模式下
FormItem会接管控件,自动给表单控件添加相应的value和onChange,所有的数据收集都由Form内部完成。
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
- 通过 useForm() 获取 formInstance 实例,form表单的数据也在实例上
- formInstance 实例对外提供了全局的方法如 setFieldsValue 、 getFieldsValue
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
- 将formInstance实例进行传递
- 然后渲染子节点
创建公用的Context
const FieldContext = React.createContext({});
阻止onSubmit默认事件,并执行Form的onSubmit方法,传入最新的表单数据,将formInstance实例传递下去。
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
- 调用cloneElement给子组件添加value和onChange的props
- 在onChange时调用setFieldsValue更新表单数据
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
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实现收集数据的功能。这里仅做了一个简单实现,还有许多特性有待拓展。