受控表单
状态变化交由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 收集。
- 受控模式下
FormItem会接管控件,自动给表单控件添加相应的 value和 onChange,所有的数据收集都由 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() 获取 formInstance 实例,form表单的数据也在实例上
- formInstance 实例对外提供了全局的方法如 setFieldsValue 、 getFieldsValue
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) => { this.store = { ...this.store, ...newStore, } } getForm = () => ({ getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, }); }
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] }
|
- 将formInstance实例进行传递
- 然后渲染子节点
创建公用的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> ) }
|
- 调用cloneElement给子组件添加value和onChange的props
- 在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; 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实现收集数据的功能。这里仅做了一个简单实现,还有许多特性有待拓展。