使用create-react-app初始化项目后创建MyRcFieldForm文件,代码如下
import React, { Component, useEffect } from 'react';
import Form, { Field } from 'rc-field-form';
//import Form, { Field } from './components/my-rc-field-form/';
import Input from './components/Input';
const nameRules = { required: true, message: '请输入姓名!' };
const passworRules = { required: true, message: '请输入密码!' };
export default function MyRCFieldForm(props) {
const [form] = Form.useForm();
const onFinish = (val) => {
console.log('onFinish', val); //sy-log
};
// 表单校验失败执行
const onFinishFailed = (val) => {
console.log('onFinishFailed', val); //sy-log
};
useEffect(() => {
console.log('form', form); //sy-log
form.setFieldsValue({ username: 'default' });
}, []);
return (
<div>
<h3>MyRCFieldForm</h3>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
<Field name='username' rules={[nameRules]}>
<Input placeholder='input UR Username' />
</Field>
<Field name='password' rules={[passworRules]}>
<Input placeholder='input UR Password' />
</Field>
<button>Submit</button>
</Form>
</div>
);
}
Input组件代码
import React from 'react';
const Input = (props) => {
return <input {...props} />;
};
class CustomizeInput extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { value = '', ...otherProps } = this.props;
return (
<div style={{ padding: 10 }}>
<Input style={{ outline: 'none' }} value={value} {...otherProps} />
</div>
);
}
}
export default CustomizeInput;
上面文件中的Form,Field组件是从rc-field-form
库中引入的,接下来我们要自己实现这两个组件。
让我们先来回顾一下antd4.0中表单组件的使用方法,首先使用Form
组件包裹自组件,给Form
组件可以添加onFinish
,onFieldsChange
等事件,在这些事件中可以监听到表单的改变获取到表单值然后做相应的处理。我们还可以使用Form.useForm
方法获取表单实例,通过该实例中的方法(setFieldsValue,getFieldsValue,validate
)我们可以获取、设置、校验表单值。然后使用Form.Item
组件包裹表单组件,给Form.Item
组件添加上name
,rules
等属性。然后当表单被输入是我们可以获取到表单的内容。
const Demo = () => {
const [form] = Form.useForm();
const handleChange = values => console.log(values);
return (
<Form form={form} onChange={handlChange}>
<Form.Item name="usename" >
<Input />
</Form.Item>
</Form>
)
}
实现思路:首先我们需要将表单组件的输入值存储到一个公共的位置 store
,然后,暴露出修改方法给表单组件,当表单组件Input
的 onChange
事件触发后调用修改方法(setFieldsValue
)将新的值更新到store
,然后重新触发组件渲染将视图同步。我们通过Form.useForm
创建store
的实例,然后通过React
中的context
将实例传递给各个Form.Item
组件。在Form.Item
组件中我们需要为表单组件Input
实现onChange
事件,将改变通过context
中的实例方法更新到store
当中,最后重新渲染自己实现视图同步。
const FormContext = React.createContext();
export default FormContext;
import FormContext from './context'
const Form = ({children, form}) => {
return (
<FormContext.Provider value={form}>
<form>{children}</form>
</FormContext.Provider>
)
}
import FormContext from './context'
class Field extends Component {
static contextType = FormContext
getControled = () => {
const {name} = this.props;
return {
value: '',
onChange: e => {console.log(e.target.value}
}
}
render() {
const {children} = this.props;
const newChildren = React.cloneElement(children, {...this.getControled()})
return newChildren
}
}
class FieldStore {
constructor() {
this.store = {};
}
getFieldValue = (name) => {return this.store[name]}
getFieldsValue = () => {return this.store}
setFieldsValue = (newValues) => {
this.store = {
...this.store,
...newValues
}
}
setFieldValue = (newValues) => {
this.store = {
...this.store,
...newValues
}
}
validate = () => {}
getForm = () => {
return {
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
setFieldValue: this.setFieldValue,
validate: this.validate,
}
}
}
const useForm = (form) => {
const formRef = React.useRef();
if (!formRef.current) {
if (form) {
formRef.current = form
} else {
formRef.current = new FieldStore()
}
}
return [formRef.current]
}
export default useForm;
Field
组件还没有实现获取到store
里面的value
以及当chang
事件触发后将值更新到store
里面最后自己更新自己实现视图同步的功能,接下来我们一步一步来实现。context
,于是我们可以从表单实例里面获取到操作store的方法,首先在getControled
方法中通过getFieldsValue
方法获取到该组件组件对应的值赋值给value
,然后在onChange
事件里面将e.target.value
通过setFieldsValue
更新store
。代码如下 getControled = () => {
const {name} = this.props;
const {getFieldValue, setFieldsValue} = this.context;
return {
value: getFieldValue(name),
onChange: e => {
setFieldsValue(e.target.value)
}
}
}
接下来还剩更新自己,我们可以在store
里面维护一个实例数组entities
获取到所有的表单组件实例,然后在setFieldsValue
方法调用时拿到对应的实例调用他的更新方法将其更新,组件自己更新时我们使用到了组件的foruceUpdate
方法,由于只需要注册实例一次,我们在Field
组件的componentWillMount
生命周期当中将该组件的实例也就是this
加到entities
里面。这里还有一个问题不要忘了,当该组件被隐藏时需要将该其实例从entities
里面删掉,所以我们还要在组件的componentWillUnmount
方法里取消注册。是不是很简单呢,请看代码~
constructor() {
this.store = {};
this.entities = [];
}
registerEntity = (entity) => {
this.entities.push(entity);
return () => {
const name = entity.props.name;
this.entities = this.entities.filter(item => item !== entity)
delete this.store[name]
}
}
setFieldsValue = (newValues) => {
this.store = {
...this.store,
...newValues
}
this.entities.forEach(entity => {
const {name} = entity.props
if (newValues.includes(name)) {
entity.fourceUpdate()
}
})
}
componentWillMount() {
const {registerEntity} = this.context;
this.cancelRegister = registerEntity(this);
}
componentWillUnmount() {
this.cancelRegister();
}
fourceUpdate() {
this.forceUpdate();
}
到这里我们的表单组件已经基本可以使用了,校验功能我们只需要在validate
方法里面拿到对应的校验规则rules
对store
里面的值进行校验并返回对应的信息就可以了,这里就不写了。
最后我们来实现一下Form组件的提交方法,antd
中的Form
组件上有很多的事件例如onFinish, onFinishFailed,onFieldsChange
等,我们在store
里面再维护一个callbacks
对象来存储这些事件。
constructor() {
this.store = {};
this.entities = [];
this.callbacks = {}
}
setCallback = callback => {
this.callbacks = {
...this.callbacks,
...callback
};
};
submit = () => {
console.log("this.", this.fieldEnetities); //sy-log
let err = this.validate();
// 在这里校验 成功的话 执行onFinish ,失败执行onFinishFailed
const {onFinish, onFinishFailed} = this.callbacks;
if (err.length === 0) {
// 成功的话 执行onFinish
onFinish(this.getFiledsValue());
} else if (err.length > 0) {
// ,失败执行onFinishFailed
onFinishFailed(err);
}
};
export default function Form({form, children, onFinish, onFinishFailed}) {
const [formInstance] = useForm(form);
formInstance.setCallback({
onFinish,
onFinishFailed
});
return (
<form
onSubmit={event => {
event.preventDefault();
formInstance.submit();
}}>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
);
}
以上就是antd4中Form、Form.Item组件的基本实现啦,由于上面的代码是我写该文章时现写的可能有错误,下面是完整代码~
import React from 'react';
import FieldContext from './context';
import { useForm } from './useForm';
const Form = ({ children, form }) => {
const [formInstance] = useForm(form);
const onSubmit = (e) => {
e.preventDefault();
formInstance.submit();
};
return (
<form onSubmit={onSubmit}>
<FieldContext.Provider value={formInstance}>{children}</FieldContext.Provider>
</form>
);
};
export default Form;
import React, { useEffect } from 'react';
import FieldContext from './context';
class Field extends React.PureComponent {
static contextType = FieldContext;
componentWillMount() {
const { registerEneity } = this.context;
this.cancelRegister = registerEneity(this);
}
componentWillUnmount() {
this.cancelRegister();
}
filedFourceUpdate() {
this.forceUpdate();
}
getControled = () => {
const { getFieldValue, setFieldsValue, getFieldsValue } = this.context;
const { name } = this.props;
return {
value: getFieldValue(name),
onChange: (e) => {
setFieldsValue({ [name]: e.target.value });
console.log(getFieldsValue());
},
};
};
render() {
const { children } = this.props;
const newChildren = React.cloneElement(children, { ...this.getControled() });
return <>{newChildren}</>;
}
}
export default Field;
import React, { useEffect } from 'react';
class formStore {
constructor() {
this.store = {};
this.entities = [];
this.callbacks = {};
}
registerEneity = (entity) => {
this.entities.push(entity);
return () => {
this.entities = this.entities.filter((item) => item !== entity);
delete this.store[entity.props.name];
};
};
getFieldsValue = () => {
return this.store;
};
setFieldsValue = (newVals) => {
this.store = {
...this.store,
...newVals,
};
this.entities.forEach((entity) => {
const name = entity.props.name;
if (Object.keys(newVals).includes(name)) entity.filedFourceUpdate();
});
};
getFieldValue = (name) => {
console.log(this);
return this.store[name];
};
setFieldValue = () => {
console.log('setFieldsValue');
};
validate = () => {};
setCallbacks = (callbacks) => {
this.callbacks = {
...this.callbacks,
...callbacks,
};
};
submit = () => {
console.log(this.store);
};
getForm = () => {
return {
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
getFieldValue: this.getFieldValue,
setFieldValue: this.setFieldValue,
validate: this.validate,
registerEneity: this.registerEneity,
setCallbacks: this.setCallbascks,
submit: this.submit,
};
};
}
const useForm = (form) => {
const formInstance = React.useRef();
if (!formInstance.current) {
if (form) {
formInstance.current = form;
} else {
const store = new formStore();
formInstance.current = store.getForm();
}
}
return [formInstance.current];
};
export { useForm };
你以为这样就完了吗,不不不。接下来我们看简单看一看antd3中Form组件的实现
在antd3
中使用高阶组件的方式实现Form
表单,我们简单说一下高阶组件的实现思路,我们使用一个createForm
高阶组件,它返回一个类组件,像上面一样我们需要一个地方存储所有的表单值以及创建修改值的方法,我们在createForm
里面用this.state
存储表单值,并使用getFieldDecorator
方法为input
组件添加onChange
事件,以及value
,value
从state
中获取,这样通过setState方法改变值时视图也会更新,当input
值改变时通过setState
将改变更新到state
上面。setFieldsValue、getFieldsValue
这些方法很简单就不说了。
代码如下
import React, {Component} from "react";
export default function createForm(Cmp) {
return class extends Component {
constructor(props) {
super(props);
this.state = {};
this.options = {};
}
handleChange = e => {
const {name, value} = e.target;
this.setState({[name]: value});
};
getFieldDecorator = (field, option) => InputCmp => {
this.options[field] = option;
return React.cloneElement(InputCmp, {
name: field,
value: this.state[field] || "",
onChange: this.handleChange
});
};
setFieldsValue = newStore => {
this.setState(newStore);
};
getFieldsValue = () => {
return this.state;
};
validateFields = callback => {
let err = [];
// 校验 检验规则 this.options
// 校验的值是this.state
for (let field in this.options) {
// 判断state[field]是否是undefined
// 如果是undefind err.push({[field]: 'err})
if (this.state[field] === undefined) {
err.push({
[field]: "err"
});
}
}
if (err.length === 0) {
// 校验成功
callback(null, this.state);
} else {
callback(err, this.state);
}
};
getForm = () => {
return {
form: {
getFieldDecorator: this.getFieldDecorator,
setFieldsValue: this.setFieldsValue,
getFieldsValue: this.getFieldsValue,
validateFields: this.validateFields
}
};
};
render() {
return <Cmp {...this.props} {...this.getForm()} />;
}
};
}
使用代码
import React, {Component} from "react";
// import {createForm} from "rc-form";
import createForm from "../components/my-rc-form/";
import Input from "../components/Input";
const nameRules = {required: true, message: "请输入姓名!"};
const passworRules = {required: true, message: "请输入密码!"};
@createForm
class MyRCForm extends Component {
constructor(props) {
super(props);
// this.state = {
// username: "",
// password: ""
// };
}
componentDidMount() {
this.props.form.setFieldsValue({username: "default"});
}
submit = () => {
const {getFieldsValue, validateFields} = this.props.form;
// console.log("submit", getFieldsValue()); //sy-log
validateFields((err, val) => {
if (err) {
console.log("err", err); //sy-log
} else {
console.log("校验成功", val); //sy-log
}
});
};
render() {
console.log("props", this.props); //sy-log
// const {username, password} = this.state;
const {getFieldDecorator} = this.props.form;
return (
<div>
<h3>MyRCForm</h3>
{getFieldDecorator("username", {rules: [nameRules]})(
<Input placeholder="Username" />
)}
{getFieldDecorator("password", {rules: [passworRules]})(
<Input placeholder="Password" />
)}
<button onClick={this.submit}>submit</button>
</div>
);
}
}
export default MyRCForm;