仿antd Form.create实现双向绑定

3,037 阅读3分钟

antd的Form.create使用

首先来看antd里Form的使用案例。这里class函数使用Form.create()包装,经过Form.create()包装后的组件会自带this.props.form属性,这里用到高阶组件的链式调用(注:高阶组件本身是对装饰器模式的应用,可以利用ES7中的装饰器语法来更优雅地书写代码,装饰器只能用在class上,执行顺序从下往上)。

  • getFieldDecorator 用于和表单进行双向绑定
  • getFieldsValue 获取一组输入控件的值,若不传入参数,则默认获取全部组件的值
  • getFieldValue 获取一个输入控件的值
  • validateFields 校验并获取一组输入域的值与error,若fieldName参数为空,则校验全部组件
import React, {Component} from "react";
import {Form, Input, Icon, Button} from "antd";

///校验规则
const nameRules = {required: true, message: "please input ur name"};
const passwordRules = {required: true, message: "please input ur password"};

@Form.create({})
class FormPage2 extends Component {
  submit = () => {
    const {getFieldsValue, getFieldValue, validateFields} = this.props.form;
    validateFields((err, values) => {
      if (err) {
        console.log("err", err); 
      } else {
        console.log("success", values);
      }
    });
    // console.log("submit", getFieldsValue(), getFieldValue("name")); 
  };
  render() {
    console.log("props", this.props); 
    const {getFieldDecorator} = this.props.form;
    return (
      <div>
        <h3>FormPage2</h3>
        <Form>
          <Form.Item>
            {getFieldDecorator("name", {rules: [nameRules]})(
              <Input placeholder="please input ur name" />
            )}
          </Form.Item>
          <Form.Item>
            {getFieldDecorator("password", {rules: [passwordRules]})(
              <Input type="password" placeholder="please input ur password" />
            )}
          </Form.Item>
          <Button type="primary" onClick={this.submit}>
            提交
          </Button>
        </Form>
      </div>
    );
  }
}

export default FormPage2;

使用HOC高阶组件的注意事项: HOC是React中复用组件的一种高级技巧,自身并不是react API中的一部分,是一种基于react的组合特性而形成的设计模式。

  • 不要在render方法中使用HOC React的diff算法使用标识来确定他是应该更新现有子树还是应该丢弃然后挂载新子树,如果从render返回的组件和前一个渲染中的组件相同,react通过将子树与新子树进行区分来递归更新子树,如果他们不相等则完全卸载前一个子树。重新挂载组件会导致该组件及其所有子组件的状态丢失。

仿照antd原理实现自己的表格双向绑定

MyFormPage组件引入kFormCreate高阶函数来修饰,扩展现有表单,实现双向绑定。

import React, {Component} from "react";
import kFormCreate from "../components/kFormCreate";

const nameRules = {required: true, message: "please input ur name"};
const passwordRules = {required: true, message: "please input ur password"};

@kFormCreate
class MyFormPage extends Component {
  submit = () => {
    const {getFieldsValue, getFieldValue, validateFields} = this.props;
    validateFields((err, values) => {
      if (err) {
        console.log("err", err); 
      } else {
        console.log("success", values); 
      }
    });
    console.log("submit", getFieldsValue(), getFieldValue("password"));
  };
  render() {
    console.log("props", this.props); 
    const {getFieldDecorator} = this.props;
    return (
      <div>
        <h3>MyFormPage</h3>
        {getFieldDecorator("name", {rules: [nameRules]})(
          <input type="text" placeholder="please input ur name" />
        )}
        {getFieldDecorator("password", {rules: [passwordRules]})(
          <input type="password" placeholder="please input ur password" />
        )}
        <button onClick={this.submit}>提交</button>
      </div>
    );
  }
}

export default MyFormPage;

kFormPage组件代码实现如下:

import React, {Component} from "react";

export default function kFormCreate(Cmp) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {};
      this.options = {};
    }
    handleChange = e => {
      // setState name value
      let {name, value} = e.target;
      this.setState({[name]: value});
    };
    // 高阶组件再接收一个组件,返回一个新组件  redux中的connect也是高阶组件
    getFieldDecorator = (field, option) => {
      this.options[field] = option;
      return InputCmp => {
        //克隆一份
        return React.cloneElement(InputCmp, {
          name: field,
          value: this.state[field] || "",
          onChange: this.handleChange
        });
      };
    };
    // 把注册的字段都收集起来,然后return出去
    getFieldsValue = () => {
      return {...this.state};
    };
    getFieldValue = field => {
      return this.state[field];
    };
    validateFields = callback => {
      // 校验错误信息
      const errors = {};
      const state = {...this.state};
      for (let name in this.options) {
        if (state[name] === undefined) {
          // 没有输入,判断为不合法
          errors[name] = "error";
        }
      }
      if (JSON.stringify(errors) === "{}") {
        // 合法
        callback(undefined, state);
      } else {
        callback(errors, state);
      }
    };
    render() {
      return (
        <div className="border">
          <Cmp
            getFieldDecorator={this.getFieldDecorator}
            getFieldsValue={this.getFieldsValue}
            getFieldValue={this.getFieldValue}
            validateFields={this.validateFields}
          />
        </div>
      );
    }
  };
}