美文网首页
React 01 - 简单实现rc-field-form

React 01 - 简单实现rc-field-form

作者: lzb30 | 来源:发表于2020-06-29 16:51 被阅读0次

先看下怎么使用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
    };
  };
}

相关文章

网友评论

      本文标题:React 01 - 简单实现rc-field-form

      本文链接:https://www.haomeiwen.com/subject/jcigfktx.html