React组件化设计下篇

1,460 阅读5分钟

承接上篇

组件化实战

我们就从常见的表单组件的设计与实现来进行组件化的实战讲解吧~

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树上另外一个角落。

最后

国庆快乐!!多陪陪家人,开心就好!