React : rc-form 源码简单实现

antd form.create()表单数据绑定

git地址>>>

实现的主要原理:利用高阶组件对原组件props属性方法再封装,可以理解为组件/函数方法中传参为组件,外层可嵌套多层函数或组件;

创建组件类
/**
 * 模拟antd下的Form下挂载create方法
 * 使用方法Form.create(component)  或  @Form.create    class Dmo extent Component{} 
 */
class Form extends React.PureComponent { }

Es6 @可修饰类和类里面的属性,无法修饰函数:

  1. 修饰器只能用于类和类的方法,不能用于修饰函数,因为存在函数提升。
  2. 由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问 题。
高阶组件属性代理

import React  from "react";
import fieldStore from "./fieldStore";
import { comparisonObject } from "./utils/common";
Form.create = (option = {}) => {
  const {
    scrollIntoView, // 表单提交异常时滚动表单项到视图中央
    callback_onErr, // 接收表单异常处理方法
  } = option;
  return (WrappedComponent) => {
    return class extends React.Component {
      constructor() {
        super();
        this.state = {};
        this.fieldStore = fieldStore()
      }
      /** 表单容器 */
      getFieldDecorator = (key, config = { rules: [], initialValue }) => {
        if (!key) throw Error("getFieldDecorator Error: lost formItem key");
        let rules = this.fieldStore.rules;
        let store = this.fieldStore.store;
        let initialMata = this.fieldStore.initialMata;
        if (rules[key] === undefined || (rules[key].length && !comparisonObject(rules[key][0], config.rules[0]))) {
          this.fieldStore.dispatchRules(key, config.rules);
        }
        if (!(key in initialMata)) {
          this.fieldStore.dispatchInitialMeta(key, config.initialValue);
        }
        return (ComponentF) => {
          const onChange = (v) => {
            if (ComponentF.props.onChange) ComponentF.props.onChange(v);
            const type = Object.prototype.toString.call(v);
            this.fieldStore.dispatchStore(key, type === "[object Object]" ? (v.target.value || e.target.checked) : v);
            this.forceUpdate(); // 强制属性刷新
          }
          const propsNew = {
            ...ComponentF.props,
            onChange: e => onChange(e),
            defaultValue: initialMata[key] || ComponentF.value,
          }
          let EleType = ComponentF.type.name;
          if (typeof ComponentF.type === 'function') propsNew.value = store[key] || initialMata[key] || ComponentF.value;
          if (EleType === "Switch") propsNew.checked = store[key] || initialMata[key] || ComponentF.checked;
          if (scrollIntoView) {
            propsNew.id = `${key}ByGetFieldDecorator`;
          }
          const newTree = React.cloneElement(ComponentF, propsNew, ComponentF.props.children);
          return newTree;
        }
      }

      validateFieldsWraped = (submit) => {
        if (this.state.submitting) return;
        this.fieldStore.validateFields((errMess, store) => {
          if (scrollIntoView) {
            return document.querySelector(`#${errKey}ByGetFieldDecorator`).scrollIntoView({ block: "center", behavior: "smooth" });
          }
          if (callback_onErr && errMess) {
            return callback_onErr(errMess);
          }
          if (errMess) return console.error(errMessage);
          this.setState({ submitting: false })
          submit(errMess, store);
        })
      }

      submitting = () => {
        return Boolean(this.state.submitting)
      }

      addtionaProps = () => {
        return {
          getFieldDecorator: this.getFieldDecorator,
          setFieldsValue: this.fieldStore.setFieldsValue,
          getFieldValue: this.fieldStore.getFieldValue,
          validateFields: this.validateFieldsWraped,
          resetFields: this.fieldStore.resetFields,
          store: this.fieldStore.store,
          submitting: this.submitting(),
        }
      }

      render() {
        var props = { form: this.addtionaProps() };
        return (
          <WrappedComponent
            {...this.props}
            {...props}
          />
        )
      }
    }
  }
}
fieldStore
/**
 * 表单仓库
 */
class FieldStore {
  constructor(fields) {
    this.store = { ...fields };
    this.rules = {};
    this.initialMata = {};
  }
  dispatchStore = (key, value) => {
    this.store[key] = value;
  }

  dispatchInitialMeta = (key, value) => {
    this.initialMata[key] = value;
  }

  dispatchRules = (key, rule) => {
    this.rules[key] = rule;
  }

  getFieldValue = (key) => {
    return this.store[key] || this.initialMata[key];
  }

  setFieldsValue = (props) => {
    this.store = {
      ...this.store,
      ...props,
    }
  }

  resetFields = () => {
    this.store = { ...fields };
    this.rules = {};
    this.initialMata = {};
  }

  validateFields = (submitting) => {
    let errFlag = false,
      errMessage = "",
      errKey = "",
      rules = this.rules;

    let store = Object.assign(this.initialMata, this.store);
    for (let i = 0; i < Object.keys(store).length; i++) {

      let item = store[Object.keys(store)[i]];
      if (rules && rules[Object.keys(store)[i]]) {
        let r = rules[Object.keys(store)[i]];

        if (r && r.length && r[0].required && (!item && item !== 0 && item !== false)) {
          errFlag = true;
          errMessage = r[0].message || "表单填写不完全";
          errKey = Object.keys(store)[i];
        } else if (r.length === 2 && r[1].validator) { // 自定义校验可正则
          function call(mess) {
            if (mess) {
              errMessage = mess || "表单项格式不正确";
              errFlag = true;
              errKey = Object.keys(store)[i];
            }
          }
          // 传入表单值、回调函数, 回调函数是否返回errMessage控制
          try {
            r[1].validator(item, call);
          } catch (error) {
            errMessage = error;
          }
        }
        if (errMessage) break;
      }
    }
    submitting(errMessage, store);
  }
}

export default function createFieldsStore(fields) {
  return new FieldStore(fields);
}
使用
import { Button, Toast, Picker, List } from "antd-mobile";
import Form from "./components/Form";
import React from "react";

@Form.create({
  callback_onErr: (mess) => {
    console.log(mess)
    Toast.fail(mess)
  }
})
class Demo extends React.Component {

  submit = () => {
    const { form: { validateFields } } = this.props;
    validateFields((err, fieldsValue) => {
      if (err) return;
      console.log(fieldsValue, "表单数据集合》》》》》》》》》》》》》》》》》》》")
    })
  }
  render() {
    const { form: { getFieldDecorator, getFieldValue, store } } = this.props;
    return (
      <div>
        <div>
          {
            getFieldDecorator("a", {
              initialValue: [1],
              rules: [{ required: true, message: "手机号不能为空" }],
            })(
              <Picker
                data={[{ label: "是", value: 2 }, { label: "是213", value: 1 }]}
                cols={1}
              >
                <List.Item arrow="horizontal"></List.Item>
              </Picker>
            )
          }
        </div>
        <div>
          {
            getFieldValue('a') == 1 ? getFieldDecorator("bbbbbbbbb", {
              initialValue: 'bbbbbbbbb',
              rules: [{ required: true, message: "手机号不能为空" }],
            })(
              <input placeholder="请输入手机号" />
            ) : ''
          }
        </div>
        <div>
          {
            getFieldValue('a') == 2 ? getFieldDecorator("ccccccccc", {
              initialValue: 'ccccccccc',
              rules: [{ required: true, message: "手机号不能为空" }],
            })(
              <input placeholder="请输入手机号" />
            ) : ''
          }
        </div>
        <Button onClick={this.submit}>提交</Button>
      </div>
    )
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271