承接上篇
组件化实战
我们就从常见的表单组件的设计与实现来进行组件化的实战讲解吧~
antd表单使用
// 实现用户名密码登录,并实现校验
// FormPage.js
import React, {Component, useEffect } from 'react';
import {Form, Input, Button } from 'antd';
const FormItem = Form.Item;
const nameRules = { required: true, message: '请输入姓名:'};
const passwordRules = { required, message: '请输入密码:'}
export default class AntdFormpage extends Component {
formRef = React.createRef();
componentDidMount() {
this.formRef.current.setFieldsValue({ name: 'default'});
}
onReset = () => {
this.formRef.current.resetFields();
}
onFinish = val => {
console.log('onFinish', val)
}
onFinishFailed = val => {
console.log('onFinishedFailed', val)
}
render() {
return (
<div>
<h3>AntdFormPage</h3>
<Form
ref={this.formRef}
onFinish= {this.onFinish}
onFinishFailed = {this.onFinishedFailed}
onReset= {this.onReset}
>
<FormItem label="姓名" name="name" rules={[nameRules]}>
<Input placeholder="name input placeholder"/>
</FormItem>
<FormItem label="密码" name="password" rules={[passwordRules]}>
<Input placeholder="password input placeholder" />
</FormItem>
<FormItem>
<Button type="primary" size="large" htmlType="subnit">Submit</Button>
</FormItem>
<FormItem>
<Button type="default" size="large" htmlType="reset">Reset</Button>
</FormItem>
</Form>
</div>
)
}
}
function实现: 注意 useForm 是React Hooks的实现,只能用于函数组件。
export default function AntdFormPage(props) {
const [form] = Form.useForm();
const onFinish = val => {
console.log('onFinish', val);
}
const onFinishFailed = val => {
console.log('onFinishFailed', val);
}
const onReset = () => {
form.resetFields();
}
useEffect(() => {
form.setFieldsValue({name: 'default'});
}, [])
return (
<Form
form={form}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
onReset={onReset}
>
<FormItem label="姓名" name="name" rules={[nameRules]}>
<Input placeholder="name input placeholder" />
</FormItem>
<FormItem label="密码" name="password" rules={[passworRules]}>
<Input placeholder="password input placeholder" />
</FormItem>
<FormItem>
<Button type="primary" size="large" htmlType="submit">Submit</Button>
</FormItem>
<FormItem>
<Button type="default" size="large" htmlType="reset">Reset</Button>
</FormItem>
</Form>
)
}
antd4表单组件实现
antd的表单基于rc-field-form, github源码地址 安装rc-field-form,yarn add rc-field-form。 使用useForm,仅限function:
import React, {Component, useEffect} from 'react';
import Form, {Field} from '../components/my-rc-fieldd-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);
}
const onFinishFailed = val => {
console.log('onFinishFailed', val);
}
useEffect(() => {
form.setFieldsValue({userName: 'default'});
}, [])
return (
<div>
<h3>MyFieldForm</h3>
<Form from={form}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Field name="username" rules={[nameRules]}>
<Input placeholder="input UR username" />
<Field>
<Field>
<Input placeholder="input UR password" />
</Field>
<button>Submit</button>
</Form>
</div>
)
}
class实现
export default class MyRCFieldForm extends Component {
formRef = React.createRef();
componentDidMount() {
this.formRef.current.setFieldsValue({username: 'default'});
}
onFinish = val => {
console.log('onFinish', val);
}
onFinishFailed = val => {
console.log('onFinishFailed', val);
}
render() {
return (
<div>
<h3>MyRcFieldForm</h3>
<Form
ref={this.formRef}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Filed name="username" rules={[nameRules]}>
<Input placeholder="username"/>
</Field>
<Field name="password" rules={[passworRules]}>
<Input placeholder="Password" />
</Field>
<button>Submit</button>
</Form>
</div>
)
}
}
实现my-rc-field-form
实现Form/Index
import React from 'react';
import _Form from './Form';
import Field from './Field';
import useForm from './useForm';
const Form = React.forwardRef(_Form);
Form.field = Field;
Form.useForm = useForm;
export { Field, useForm };
export default Form;
实现Form
import React from 'react';
import useForm from './useForm';
import FieldContext from './FieldContext';
export default function Form({children, onFinish, onFinishFailed, form}, ref) {
const [formInstance] = useForm(form)
React.useImperativeHandle(ref, () => formInstance);
formInstance.setCallback({
onFinish,
onFinishFailed,
})
return (
<Form
onSubmit={
event => {
event.preventDefault();
event.stopPropagation();
formInstance.submit();
}
}
>
<FieldContext.Provider value={formInstance}>
{children}
</FieldContext.Provider>
</Form>
)
}
实现FieldContext
import React from 'recat';
const warnFunc = () => {
console.log('-------error--------')
}
const FieldContext = React.createContext({
registerField: warnFunc,
setFieldsValue: warnFunc,
getFieldValue: warnFunc,
getFieldsValue: warnFunc,
submit: warnFunc
})
export default FieldContext;
实现useForm
import React from 'react';
class FormStore {
constructor() {
this.store = {}; // 存储数据,比如说username,password
this.fieldEntities = [];
// callback onFinish onFinishFailed
this.callbacks = [];
}
// 注册
registerField = entity => {
this.fieldEntities.push(entity);
return () => {
thie.fieldEntities = this.fieldEntities.filter(item => item!==entity);
delete this.store[entity.props.name];
};
};
setCallback = callback => {
this.callbacks = {
...this.callbacks,
...callback,
};
};
//取数据
getFieldValue = name => {
return this.store[name];
}
getFieldsValue = () => {
return this.store;
}
/ 设置数据
setFieldsValue = newStore => {
this.store = {
...this.store,
...newStore,
}
this.fieldEntities.forEach(entity => {
const {name} = entity.props;
Object.keys(newStore).forEach(key => {
if(key === name) {
entity.onStoreChange();
}
})
})
};
validate = () => {
let err = [];
this.fieldEntities.forEach(entity => {
const {name, rules} = entity.props;
let value = this.getFieldValue(name);
let rule = rules && rules[0];
if(rule && rule.required && (value === undefined || value === '')) {
// 出错
err.push({
[name]: rules.messagee,
value
})
}
});
return err;
};
submit = () => {
console.log('this.fieldEntities', this.fieldEntities);
let err = this.validate();
// 在这里校验成功的话, 执行onFinish,失败执行onFinishFailed
const {onFinish, onFinishFailed} = this.callbacks;
if(err.length === 0){
//成功的话,执行onFinish
onFinish(this.getFieldsValue());
}else if(err.length > 0) {
// 失败执行onFinishFailed
onFinishFailed(err);
}
};
getForm = () => {
return {
registerField: this.registerField,
setCallback: this.setCallback,
submit: this.submit,
getFieldValue: this.getFieldValue,
getFiledsValue: this.getFieldsValue,
setFieldsValue: this.setFieldsValue,
};
};
// 自定义hook
export default function useForm(form) {
const formRef = React.useRef();
if(!form.current) {
if(form){
formRef.current = form;
}else {
// new一个
const formStore = new FormStore();
formRef.current = formStore.getForm();
}
}
return [formRef.current];
}
}
实现Field
import React, {Component} from 'react';
import FieldContext from './FieldContext';
export default class Field extends Component {
static contextType = FieldContext;
componentDidMount() {
const {registerField} = this.context;
this.cancelRegisterField = registerField(this);
}
componentWillUnmount() {
if(this.cancelRegisterField) {
this.cancelRegisterField();
}
}
onStoreChange = () => {
this.forceUpdate();
}
getControlled = () => {
const {name} = this.props;
const {getFieldValue, setFieldsValue} = this.context;
return {
value: getFieldValue(name), //取数据
onChange: event => {
// 存数据
const newValue = event.target.value;
setFieldsValue({[name]: newValue});
};
};
}
render() {
console.log('field render');
const {children} = this.props;
const returnChildNode = React.cloneElement(children, this.getControllled());
return returnChildNode;
}
}
实现my-rc-form
import React, {Component} from 'react';
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);
}
componentDidMount() {
this.props.form.setFieldsValue({username: 'default'});
}
submit = () => {
const {getFieldsValue, validateFields} = this.props.form;
validateFields((err, val) => {
if(err) {
console.log('err', err);
}else {
console.log('校验成功', val);
}
})
}
render() {
console.log('props', this.props);
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;
实现
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
// 如果是undefined 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()} />;
}
}
}
弹窗类组件的设计与实现
设计思路 弹窗类组件的要求弹窗内容在A处声明,却在b处展示。react中相当于弹窗内容看起来 被render到一个组件里面去,实际改变的是网页上另一处的DOM结构,这个显然不符合正常逻辑。但是通过使用框架提供的特定的API创建组件实例并制定挂载目标仍可完成任务。
// 常见用法如下:Dialog在当前组件声明,但是却在body中另一个div中显示
import React, {Component} from 'react';
import Dialog from '../components/Dialog';
export default class DialogPage extends Component {
constructor(props) {
super(props);
this.state = {
showDialog: false
};
}
render() {
const {showDialog} = this.state;
return (
<div>
<h3>DialogPage</h3>
<button onClick={()=> this.setState({
showDialog: !showDialog
})}>
toogle
</button>
{showDialog && <Dialog />}
</div>
)
}
}
具体实现:Portal
传送门,react v16之后出现的portal可以实现内容传送功能。 Dialog组件
// Dialog.js
import React, {Component} from 'react';
import { createPortal } from 'react-dom';
export default class Dialog extends Component {
constructor(props) {
super(props);
const doc = window.document;
this.node = doc.createElement('div');
doc.body.appendChild(this.node);
}
componentWillUnmount(){
window.document.body.removeChild(this.node);
}
render() {
const {hideDialog} = this.props;
return createPortal(
<div className="dialog">
{this.props.children}
{
typeof hideDialog === 'function' && (
<button onClick={hideDialog}>关掉弹窗</button>
)
}
</div>,
this.node,
)
}
}
.dialog {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
line-height: 30px;
width: 400px;
height: 300px;
transform: translate(50%,50%);
border: solid 1px gray;
text-align: center;
}
总结一下:
Dialog做的事情是通过调用createPortal把要画的东西画在DOM树上另外一个角落。
最后
国庆快乐!!多陪陪家人,开心就好!