先看下怎么使用rc-field-form,创建myRCFieldForm.js
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>
);
}
看下我们大概需要实现哪些API,有Form,Field,useForm等。那么我们先来搭一个简单的架子。
1.搭架子
在components里面新建一个目录my-rc-field-form,创建index.js,Form.js,Field.js,useForm.js。
Form.js(函数式组件)
import React from "react";
export default function Form({ children }) {
return <form>{children}</form>;
}
Field.js
import React, {Component} from "react";
export default class Field extends Component {
render() {
const {children} = this.props;
return children;
}
}
useForm.js(自定义hook)
export default function useForm() {
return [];
}
index.js
import _Form from "./Form";
import Field from "./Field";
import useForm from "./useForm";
const Form = _Form;
export {Field, useForm};
export default Form;
在myRCFieldForm.js引入我们组件的js文件
import React, {Component, useEffect} from "react";
// import Form, {Field} from "rc-field-form";
import Form, {Field} from "../components/my-rc-field-form/";
2.实现数据的存储
首先,实现Field.js,给input组件添加value属性和onChange事件
// Field.js
getControled = () => {
return {
value: '', //从store中取值
onChange: e => {
// 把新的参数值存到store中
const newValue = e.target.value;
console.log("newValue", newValue); //sy-log
}
};
};
render() {
const {children} = this.props;
const returnChildNode = React.cloneElement(children, this.getControled());
return returnChildNode;
}
解决input组件value的存储,如果把值存储在单个组件中,就没法进行通信,所以需要有一个store,可以存储所有组件的value。
在useForm.js,增加FormStore,用于存储表单的数据。
// useForm.js
import React, {useRef} from "react";
class FormStore {
constructor(props) {
this.store = {};
}
getFieldValue = name => {
return this.store[name];
};
getFieldsValue = name => {
return this.store;
};
// 修改store
setFieldsValue = newStore => {
this.store = {
// ! 调整下顺序
...newStore,
...this.store
};
};
getForm = () => {
return {
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];
}
store已经有了,那我们怎么让所有组件都可以共用这个store呢,答案就是context。
新建FieldContext.js
import React from "react";
const FieldContext = React.createContext();
export default FieldContext;
有了context,接下来改造form.js,让子组件都能共用store。
import React from "react";
import useForm from "./useForm";
import FieldContext from "./FieldContext";
export default function Form({form, children, onFinish, onFinishFailed}) {
const [formInstance] = useForm(form);
formInstance.setCallback({
onFinish,
onFinishFailed
});
return (
<form
onSubmit={e => {
e.preventDefault();
formInstance.submit();
}}>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
);
}
这时子组件也能访问store了,我们来改造Field.js,完成value的读取和设置。
export default class Field extends Component {
static contextType = FieldContext;
getControled = () => {
const {getFieldValue, setFieldsValue} = this.context;
const {name} = this.props;
return {
value: getFieldValue(name), //从store中取值
onChange: e => {
// 把新的参数值存到store中
const newValue = e.target.value;
setFieldsValue({[name]: newValue});
console.log("newValue", newValue); //sy-log
}
};
};
...
}
value的读取已经完成,但是组件不会更新,在设置完成后需要对组件进行更新。
3.状态更新
主要解决两个问题,一个是组件如何更新,一个是组件何时更新。
- 组件如何更新,可以用forceUpdate
给Field.js增加onStoreChange方法
//Field.js
...
onStoreChange = () => {
this.forceUpdate();
};
...
- 组件何时更新
在调用sotre.setFieldsValue时,store通知对应的组件更新,这时,我们就需要用一个变量FieldEntities来保存所有的组件实例。在组件挂载的时候注册到store里面,当组件卸载的时候要把store对应的组件实例卸载掉。
//useForm.js
import React, {useRef} from "react";
class FormStore {
constructor(props) {
this.store = {};
this.fieldEntities = [];
}
// 注册组件实例,返回一个回调函数,用于删除组件实例
registerField = field => {
this.fieldEntities.push(field);
return () => {
this.fieldEntities = this.fieldEntities.filter(item => item != field);
delete this.store[field.props.name];
};
};
// 修改store
setFieldsValue = newStore => {
this.store = {
// ! 调整下顺序
...newStore,
...this.store
};
// store已经更新,但是我们希望组件也跟着更新
this.fieldEntities.forEach(enetity => {
const {name} = enetity.props;
Object.keys(newStore).forEach(key => {
if (key === name) {
enetity.onStoreChange();
}
});
});
console.log("store", this.store); //sy-log
};
// Field.js
import React, {Component, cloneElement} from "react";
import FieldContext from "./FieldContext";
export default class Field extends Component {
static contextType = FieldContext;
componentDidMount() {
// 注册组件实例
this.cancelRegister = this.context.registerField(this);
}
componentWillUnmount() {
// 卸载组件实例
if (this.cancelRegister) {
this.cancelRegister();
}
}
4.实现表单提交和校验
给Form组件实现submit事件,由于表单的默认submit事件会刷新页面,所以需要阻止默认事件。由于数据都保存在store里面,需要对store的数据进行校验,我们把submit方法放在FormStore里面实现,校验完后还要执行回调方法,所以还要增加一个setCallback方法,用于保存成功和失败的回调函数。
//Form.js
import React from "react";
import useForm from "./useForm";
import FieldContext from "./FieldContext";
export default function Form({form, children, onFinish, onFinishFailed}) {
const [formInstance] = useForm(form);
formInstance.setCallback({
onFinish,
onFinishFailed
});
return (
<form
onSubmit={e => {
e.preventDefault();
formInstance.submit();
}}>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</form>
);
}
FormStore的setCallback和submit的实现。
//useForm.js
import React, {useRef} from "react";
class FormStore {
constructor(props) {
this.store = {};
this.fieldEntities = [];
this.calllbacks = {}; // 保存成功和失败的回调函数
}
...
// 设置回调方法
setCallback = callback => {
this.calllbacks = {
...this.calllbacks,
...callback
};
};
// 表单校验
validate = () => {
let err = [];
// todo
this.fieldEntities.forEach(entity => {
const {name, rules} = entity.props;
let value = this.store[name];
let rule = rules && rules[0];
if (rule && rule.required && (value === undefined || value === "")) {
// 出错
err.push({
[name]: rules.message,
value
});
}
});
return err;
};
// 表单提交
submit = () => {
let err = this.validate();
// 成功
if (err.length === 0) {
this.calllbacks.onFinish(this.store);
} else if (err.length > 0) {
// 失败
this.calllbacks.onFinishFailed(err);
}
};
getForm = () => {
return {
setCallback: this.setCallback,
submit: this.submit,
registerField: this.registerField,
getFieldValue: this.getFieldValue,
getFieldsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue
};
};
}