手写rc-form v3 & v4

1,256 阅读4分钟

and3-form的使用以及核心代码编写

and3-form的使用

import React, { Component } from 'react';
import { createForm } from "rc-form";

import Input from '../components/Input'

const nameRule = {
  required: true,
  message: '名称必输'
}

const passwordRule = {
  required: true,
  message: '名称必输'
}

class RcFormPage extends Component {
  componentDidMount() {
    console.log('props', this.props)
  }

  submit = () => {
    const { getFieldsValue,validateFields } = this.props.form;
    validateFields((err,value) => {
      console.log(err,value)
    })
  }

  render() {
    const { getFieldDecorator } = this.props.form;
    return (
      <div>
        <h3>RcFormPage</h3>
        {getFieldDecorator('name', {
          rules: [nameRule]
        })(<Input placeholder="请输入名称"/>)}
        {getFieldDecorator('password', {
          rules: [passwordRule]
        })(<Input placeholder="请输入密码"/>)}

        <div>
          <button onClick={this.submit}>submit</button>
        </div>
      </div>
    );
  }
}
//高阶组件,传入一个自己编写的组件,返回一个带form功能的新组件
export default createForm()(RcFormPage);

antd3表单组件设计思路

  • 表单组件需要实现数据收集、校验、提交等特性,通过高阶组件扩展(接收一个组件,给组件添加form属性,返回一个新的组件)
  • 高阶组件给表单组件传递一个input组件包装函数接管其输入时间并统一管理表单数据
  • 高阶组件给表单组件传递一个校验函数使其具备数据校验功能 antd3的设计有个问题,局部的变化回引起整体的变化,因为state是保存到form里的,所以setState的时候所有的组件都会更新。

手写ant3-form核心代码

import React, { Component } from 'react';

function createForm(options) {
  return (Cmp) => {
    return class extends Component {
      state = {}
      //存放getFieldDecorator定义的option集合
      options = {}
      getForm = () => {
        return {
          getFieldsValue: this.getFieldsValue,
          getFieldValue: this.getFieldValue,
          getFieldDecorator: this.getFieldDecorator,
          setFieldValue: this.setFieldValue,
          validateField:this.validateField
        }
      }

      handleChange = (e) => {
        const { value, name } = e.target;
        this.setState({
          [name]: value
        })
      }

      getFieldDecorator = (fieldName, option) => {
        //收集option
        this.options[fieldName] = option;
        return (InputCmp) => {
          return React.cloneElement(InputCmp, {
            //将字段名和值全放在属性上
            name: fieldName,
            value: this.state[fieldName] || '',
            onChange: this.handleChange
          })
        }
      }

      //数据校验方法
      validateField = (callback) => {
        let err = [];
        for (const fieldName in this.options) {
          //获取rules:[]
          const rules = this.options[fieldName].rules;
          if(rules.length>0){
            for (let i = 0; i < rules.length; i++) {
              if(rules[i].required && this.state[fieldName] === undefined){
                err.push({[fieldName]:rules[i].message})
              }
            }
          }
        }
        if(err.length>0){
          callback(err,this.state)
        }else{
          callback(null,this.state)
        }
      }

      getFieldsValue = () => {
        return this.state;
      }

      getFieldValue = (name) => {
        return this.state[name]
      }

      setFieldValue = (newStore) => {
        this.setState(newStore)
      }

      render() {
        const form = this.getForm();
        return <Cmp {...this.props} form={form}/>
      }
    }
  }
}
export { createForm };

and4-form的使用以及核心代码编写

and4-form的使用

import React, { useEffect } from "react";
import { Form, Button, Input } from "antd";

const FormItem = Form.Item;
//校验规则
const nameRules = { required: true, message: "请输入姓名" };
const passwordRules = { required: true, message: "请输入密码" };

const AntdFormPage = () => {
  const [form] = Form.useForm();

  //提交成功事件
  const onFinish = (val) => {
    console.log("onFinish", val);
  };

  //提交失败事件
  const onFinishFailed = (val) => {
    console.log("onFinishFailed", val);
  };

  //页面初始化给usename赋值
  useEffect(() => {
    form.setFieldsValue({ username: "张三" });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <h3>AntdFormPage</h3>
      <Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed}>
        <FormItem name="username" label="姓名" rules={[nameRules]}>
          <Input placeholder="请输入名字" />
        </FormItem>
        <FormItem name="password" label="密码" rules={[passwordRules]}>
          <Input placeholder="请输入密码" />
        </FormItem>
        <FormItem>
          <Button type="primary" htmlType="submit">
            Submit
          </Button>
        </FormItem>
      </Form>
    </div>
  );
};

export default AntdFormPage;

antd4表单实现思路

  • 创建一个formStore,将所有的数据都存到store里,并提供get,set方法
  • 通过发布订阅的方式,在修改了formStore中的值之后执行forceUpdate,避免刷新所有组件

index.js

import React from "react";
import Field from "./Field";
import _Form from "./Form";
import useForm from "./useForm";

const Form = React.forwardRef(_Form);
Form.Field = Field;
Form.useForm = useForm;

export { Field, useForm };
export default Form;

Form.js

import React from 'react';
import FieldContext from "./FieldContext";
import useForm from "./useForm";

function Form({ form, children, onFinish, onFinishFailed },ref) {
  //这里分两种情况
  //1.在使用form之前已经创建了form,这里接收已经创建的form防止重复创建
  //2.之前没有调用useForm,这里调用之后隐式创建一个form
  const [formInstance] = useForm(form);

  //保存提交之后的回调方法到store
  formInstance.setCallbacks({ onFinish, onFinishFailed })

  //当使用类组件的时候只能在这生成form,如果外部要使用form的api只能使用ref抛出formInstance
  React.useImperativeHandle(ref,()=>formInstance);

  return (
    <form onSubmit={(e)=>{
      e.preventDefault();
      formInstance.onSubmit();
    }}>
      <FieldContext.Provider value={formInstance}>
        {children}
      </FieldContext.Provider>
    </form>
  );
}

export default Form;

Field.js

import React, { Component } from 'react';
import FieldContext from "./FieldContext";

class Field extends Component {
  static contextType = FieldContext;

  componentDidMount() {
    this.unRegisterFieldEntities = this.context.registerFieldEntities(this);
  }

  componentWillUnmount() {
    this.unRegisterFieldEntities();
  }

  //当组件改变时刷新组件
  onStoreChange = () => {
    this.forceUpdate();
  }

  getControlled = () => {
    const { getFieldValue, setFieldsValue } = this.context;
    const { name } = this.props;
    return {
      value: getFieldValue(name),
      onChange: (e) => {
        const newValue = e.target.value;
        //更新formStore中的值,并且更新组件
        setFieldsValue({ [name]: newValue });
      }
    }
  }

  render() {
    console.log(1)
    const { children } = this.props;
    const cloneNode = React.cloneElement(children, this.getControlled())
    return (
      cloneNode
    );
  }
}

export default Field;

useForm.js

//创建一个第三方仓库
import { useRef } from "react";

class FormStore {
  constructor() {
    //创建一个store存储数据
    this.store = {};

    //创建一个数组存放这个form中所有的元素
    this.fieldEntities = []

    this.callbacks = {}

  }

  //保存提交后的回调函数
  setCallbacks = (callback) => {
    this.callbacks = {
      ...this.callbacks,
      ...callback
    }
  }
  //注册field实例
  //订阅和取消订阅都是成对出现的
  registerFieldEntities = (field) => {
    this.fieldEntities.push(field);
    return () => {
      //方便组件卸载时取消订阅
      this.fieldEntities.filter((item) => {
        return item !== field
      })
      delete this.store[field.props.name]
    }
  }

  //获取单个字段的值
  getFieldValue = (fieldName) => {
    return this.store[fieldName];
  }

  //获取所有字段的值
  getFieldsValue = () => {
    return { ...this.store }
  }

  //设置值
  setFieldsValue = (newStore) => {
    this.store = {
      ...this.store,
      ...newStore
    }
    //更新对应的组件
    this.fieldEntities.forEach((f) => {
      //遍历需要修改的对象,找到对应的组件
      Object.keys(newStore).forEach((key) => {
        if(f.props.name === key){
          f.onStoreChange();
        }
      })
    })
    console.log('store',this.store)
  }

  //数据根据rule进行校验
  validate = () => {
    let err = []
    this.fieldEntities.forEach((field) => {
      const { rules,name } = field.props;
      const rule = rules && rules[0];
      const value = this.getFieldValue(name);
      if(rule && rule.required && (value == undefined || value === '')){
        err.push({
          [name]:rule.message,
          value
        })
      }
    })
    return err;
  }

  onSubmit = () => {
    const err = this.validate();
    if(err.length === 0){
      this.callbacks.onFinish(this.store);
    }else{
      this.callbacks.onFinishFailed(err,this.store);
    }
  }
  //暴露form中所有的api
  getForm = () => {
    return {
      getFieldValue: this.getFieldValue,
      getFieldsValue: this.getFieldsValue,
      setFieldsValue: this.setFieldsValue,
      registerFieldEntities: this.registerFieldEntities,
      setCallbacks:this.setCallbacks,
      onSubmit:this.onSubmit
    }
  }
}

//form构造器
export default function useForm(form) {
  //将form存到useRef中
  const formRef = useRef();
  //页面更新时会再次执行useForm
  //执行之前先判断是否已经存在form,保证每次用的都是同一个form
  if (!formRef.current) {
    if (form) {
      formRef.current = form;
    }else{
      const formStore = new FormStore();
      formRef.current = formStore.getForm();
    }
  }

  return [formRef.current];
}

FieldContext.js

import React from 'react'

const FieldContext = React.createContext();
export default FieldContext;