认识
2023年02月18日
一、设计思路
- 表单组件要求实现数据收集、校验、提交等特性,可通过⾼阶组件扩展
- ⾼阶组件给表单组件传递⼀个
input
组件包装函数接管其输⼊事件并统⼀管理表单数据 - ⾼阶组件给表单组件传递⼀个校验函数使其具备数据校验功能
二、实现目标
- 通过
Form.create()
创建Form
getFieldDecorator
:⽤于和表单进⾏双向绑定getFieldValue
: 获取⼀个输⼊控件的值getFieldsValue
: 获取⼀组输⼊控件的值,如不传⼊参数,则获取全部组件的值setFieldValue
:validateFields
: 校验并获取⼀组输⼊域的值与Error
,若fieldNames
参数为空,则校验全部组件
三、具体实现
细节
- 通过
getFieldDecorator
装饰Input
组件时,为什么需要用React.cloneElement
,直接用原来的Input
组件可以吗?
答: 我们需要给Input
组件添加value
、onChange
属性,但是我们又不能修改原来的组件,对原来的组件造成污染,所以我们需要一个全新的Input
组件,所以需要用cloneElement
实现
- 在设置
Input
的value
字段值时,为什么需要给定默认值?
答: React
中input
中的value
值为undefined
时就变成了非受控组件,非受控组件是不可以通过onChange
事件成为受控组件的,否则会报错。那么不让input
变为非受控组件的解决方法是在给定value
值时给一个默认值value=this.state[field]||""
(默认值为空字符串,不可以是 undefined)
import React from "react";
function MyForm(props) {
return <div>{props.children}</div>;
}
MyForm.create = () => {
return (Comp) =>
class extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.options = {};
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({ [name]: value }, () => {
this.validateField(name);
});
};
getFieldValue = (field) => {
return this.state[field];
};
getFieldsValue = () => {
return { ...this.state };
};
validateField = (field) => {
const { rules = [] } = this.options[field];
const result = rules.some((rule) => {
if (rule.required) {
if (!this.state[field]) {
this.setState({ [field + "Message"]: rule.message });
return true;
}
}
return false;
});
if (!result) {
this.setState({ [field + "Message"]: "" });
}
return !result;
};
validateFields = (callback) => {
const validateResultList = Object.keys(this.options).map((field) => {
return this.validateField(field);
});
const result = validateResultList.every((rule) => rule);
callback(!result, { ...this.state });
};
getFieldDecorator = (field, options) => {
this.options[field] = options;
return (InputComp) => {
return (
<div>
{React.cloneElement(InputComp, {
name: field,
value: this.state[field] || "",
onChange: this.handleChange,
})}
{this.state[field + "Message"] && (
<div>{this.state[field + "Message"]}</div>
)}
</div>
);
};
};
render() {
return (
<Comp
{...this.props}
validateField={this.validateField}
getFieldValue={this.getFieldValue}
getFieldsValue={this.getFieldsValue}
validateFields={this.validateFields}
getFieldDecorator={this.getFieldDecorator}
/>
);
}
};
};
MyForm.Item = (props) => {
return <div>{props.children}</div>;
};
export default MyForm;
import React from "react";
import MyForm from "./MyForm";
function App(props) {
const { getFieldValue, getFieldsValue, getFieldDecorator, validateFields } =
props;
const handleLogin = () => {
console.log("获取 username 字段值", getFieldValue("username"));
console.log("获取全部字段值", getFieldsValue());
validateFields((error, value) => {
if (error) {
console.log("验证失败");
} else {
console.log("验证成功", value);
}
});
};
return (
<MyForm>
<h3>登录表单</h3>
<MyForm.Item>
<label>用户名:</label>
{getFieldDecorator("username", {
rules: [
{
required: true,
message: "请输入用户名",
},
],
})(<input name="username" type="text" placeholder="请输入用户名" />)}
</MyForm.Item>
<MyForm.Item>
<label>密码:</label>
{getFieldDecorator("password", {
rules: [
{
required: true,
message: "请输入密码",
},
],
})(<input name="password" type="password" placeholder="请输入密码" />)}
</MyForm.Item>
<MyForm.Item>
<button onClick={() => handleLogin()}>登录</button>
</MyForm.Item>
</MyForm>
);
}
export default MyForm.create()(App);