实现antd中Form、Form.Item组件

马嘉勋
2023-12-01

实现antd中Form、Form.Item组件

初始化项目

使用create-react-app初始化项目后创建MyRcFieldForm文件,代码如下

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>
  );
}	

Input组件代码

import React from 'react';

const Input = (props) => {
  return <input {...props} />;
};
class CustomizeInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { value = '', ...otherProps } = this.props;
    return (
      <div style={{ padding: 10 }}>
        <Input style={{ outline: 'none' }} value={value} {...otherProps} />
      </div>
    );
  }
}

export default CustomizeInput;


上面文件中的Form,Field组件是从rc-field-form库中引入的,接下来我们要自己实现这两个组件。

原理以及实现思路

让我们先来回顾一下antd4.0中表单组件的使用方法,首先使用Form组件包裹自组件,给Form组件可以添加onFinish,onFieldsChange等事件,在这些事件中可以监听到表单的改变获取到表单值然后做相应的处理。我们还可以使用Form.useForm方法获取表单实例,通过该实例中的方法(setFieldsValue,getFieldsValue,validate)我们可以获取、设置、校验表单值。然后使用Form.Item组件包裹表单组件,给Form.Item组件添加上name,rules等属性。然后当表单被输入是我们可以获取到表单的内容。

const Demo = () => {
	const [form] = Form.useForm();
	const handleChange = values => console.log(values);
	return (
		<Form form={form} onChange={handlChange}>
			<Form.Item name="usename" >
				<Input />
			</Form.Item>
		</Form>
	)
}

实现思路:首先我们需要将表单组件的输入值存储到一个公共的位置 store,然后,暴露出修改方法给表单组件,当表单组件InputonChange事件触发后调用修改方法(setFieldsValue)将新的值更新到store,然后重新触发组件渲染将视图同步。我们通过Form.useForm创建store的实例,然后通过React中的context将实例传递给各个Form.Item组件。在Form.Item组件中我们需要为表单组件Input实现onChange事件,将改变通过context中的实例方法更新到store当中,最后重新渲染自己实现视图同步。

搭出Form、Field组件,useForm自定义hook的架子

  • context文件
const FormContext = React.createContext();
export default FormContext;
  • Form
import FormContext from './context'
const Form = ({children, form}) => {
	return (
		<FormContext.Provider value={form}>
			<form>{children}</form>
		</FormContext.Provider>
	)
}
  • Field
import FormContext from './context'
class Field extends Component {
	static contextType = FormContext
	getControled = () => {
		const {name} = this.props;
		return {
			value: '',
			onChange: e => {console.log(e.target.value}
		}
	}
	render() {
		const {children} = this.props;
		const newChildren = React.cloneElement(children, {...this.getControled()})
		return newChildren
	}
}
  • useForm
class FieldStore {
	constructor() {
		this.store = {};
	}
	
	getFieldValue = (name) => {return this.store[name]}
	getFieldsValue = () => {return this.store}
	setFieldsValue = (newValues) => {
		this.store = {
			...this.store,
			...newValues
		}
	} 
		setFieldValue = (newValues) => {
		this.store = {
			...this.store,
			...newValues
		}
	} 
	validate = () => {}
	
	getForm = () => {
		return {
			getFieldValue: this.getFieldValue,
			getFieldsValue: this.getFieldsValue,
			setFieldsValue: this.setFieldsValue,
			setFieldValue: this.setFieldValue,
			validate: this.validate,
		}
	}
}
const useForm = (form) => {
	const formRef = React.useRef();
	if (!formRef.current) {
		if (form) {
			formRef.current = form
		} else {
			formRef.current = new FieldStore()
		}
	}
	return [formRef.current]
}
export default useForm;

完善功能

  • Field组件
    上面的Field组件还没有实现获取到store里面的value以及当chang事件触发后将值更新到store里面最后自己更新自己实现视图同步的功能,接下来我们一步一步来实现。
    我们已经绑定了context,于是我们可以从表单实例里面获取到操作store的方法,首先在getControled方法中通过getFieldsValue方法获取到该组件组件对应的值赋值给value,然后在onChange事件里面将e.target.value通过setFieldsValue更新store。代码如下
 	getControled = () => {
		const {name} = this.props;
		const {getFieldValue, setFieldsValue} = this.context;
		return {
			value: getFieldValue(name),
			onChange: e => {
				setFieldsValue(e.target.value)
			}
		}
	}

接下来还剩更新自己,我们可以在store里面维护一个实例数组entities获取到所有的表单组件实例,然后在setFieldsValue方法调用时拿到对应的实例调用他的更新方法将其更新,组件自己更新时我们使用到了组件的foruceUpdate方法,由于只需要注册实例一次,我们在Field组件的componentWillMount生命周期当中将该组件的实例也就是this加到entities里面。这里还有一个问题不要忘了,当该组件被隐藏时需要将该其实例从entities里面删掉,所以我们还要在组件的componentWillUnmount方法里取消注册。是不是很简单呢,请看代码~

  • useForm
	constructor() {
		this.store = {};
		this.entities = [];
	}
	
	registerEntity = (entity) => {
		this.entities.push(entity);
		return () => {
			const name = entity.props.name;
			this.entities = this.entities.filter(item => item !== entity)
			delete this.store[name]
		}
	}
	setFieldsValue = (newValues) => {
		this.store = {
			...this.store,
			...newValues
		}
		this.entities.forEach(entity => {
			const {name} = entity.props
			if (newValues.includes(name)) {
				entity.fourceUpdate()
			}
		})
	} 
  • Field
componentWillMount() {
	const {registerEntity} = this.context;
	this.cancelRegister = registerEntity(this);
}

componentWillUnmount() {
	this.cancelRegister();
}

fourceUpdate() {
	this.forceUpdate();
}

到这里我们的表单组件已经基本可以使用了,校验功能我们只需要在validate方法里面拿到对应的校验规则rulesstore里面的值进行校验并返回对应的信息就可以了,这里就不写了。
最后我们来实现一下Form组件的提交方法,antd中的Form组件上有很多的事件例如onFinish, onFinishFailed,onFieldsChange等,我们在store里面再维护一个callbacks对象来存储这些事件。

  • useForm
	constructor() {
		this.store = {};
		this.entities = [];
		this.callbacks = {}
	}
	  setCallback = callback => {
	    this.callbacks = {
	      ...this.callbacks,
	      ...callback
	    };
	  };
	  submit = () => {
	    console.log("this.", this.fieldEnetities); //sy-log
	    let err = this.validate();
	    // 在这里校验 成功的话 执行onFinish ,失败执行onFinishFailed
	    const {onFinish, onFinishFailed} = this.callbacks;
	    if (err.length === 0) {
	      // 成功的话 执行onFinish
	      onFinish(this.getFiledsValue());
	    } else if (err.length > 0) {
	      // ,失败执行onFinishFailed
	      onFinishFailed(err);
	    }
	  };
  • Form
export default function Form({form, children, onFinish, onFinishFailed}) {
  const [formInstance] = useForm(form);
  formInstance.setCallback({
    onFinish,
    onFinishFailed
  });
  return (
    <form
      onSubmit={event => {
        event.preventDefault();
        formInstance.submit();
      }}>
      <FieldContext.Provider value={formInstance}>
        {children}
      </FieldContext.Provider>
    </form>
  );
}

以上就是antd4中Form、Form.Item组件的基本实现啦,由于上面的代码是我写该文章时现写的可能有错误,下面是完整代码~

完整代码

  • Form
import React from 'react';
import FieldContext from './context';
import { useForm } from './useForm';
const Form = ({ children, form }) => {
  const [formInstance] = useForm(form);

  const onSubmit = (e) => {
    e.preventDefault();
    formInstance.submit();
  };

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

export default Form;
  • Field
import React, { useEffect } from 'react';
import FieldContext from './context';
class Field extends React.PureComponent {
  static contextType = FieldContext;

  componentWillMount() {
    const { registerEneity } = this.context;
    this.cancelRegister = registerEneity(this);
  }

  componentWillUnmount() {
    this.cancelRegister();
  }

  filedFourceUpdate() {
    this.forceUpdate();
  }

  getControled = () => {
    const { getFieldValue, setFieldsValue, getFieldsValue } = this.context;
    const { name } = this.props;
    return {
      value: getFieldValue(name),
      onChange: (e) => {
        setFieldsValue({ [name]: e.target.value });
        console.log(getFieldsValue());
      },
    };
  };
  render() {
    const { children } = this.props;
    const newChildren = React.cloneElement(children, { ...this.getControled() });
    return <>{newChildren}</>;
  }
}

export default Field;
  • useForm
import React, { useEffect } from 'react';

class formStore {
  constructor() {
    this.store = {};
    this.entities = [];
    this.callbacks = {};
  }

  registerEneity = (entity) => {
    this.entities.push(entity);
    return () => {
      this.entities = this.entities.filter((item) => item !== entity);
      delete this.store[entity.props.name];
    };
  };

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

  setFieldsValue = (newVals) => {
    this.store = {
      ...this.store,
      ...newVals,
    };
    this.entities.forEach((entity) => {
      const name = entity.props.name;
      if (Object.keys(newVals).includes(name)) entity.filedFourceUpdate();
    });
  };
  getFieldValue = (name) => {
    console.log(this);
    return this.store[name];
  };

  setFieldValue = () => {
    console.log('setFieldsValue');
  };

  validate = () => {};

  setCallbacks = (callbacks) => {
    this.callbacks = {
      ...this.callbacks,
      ...callbacks,
    };
  };

  submit = () => {
    console.log(this.store);
  };

  getForm = () => {
    return {
      getFieldsValue: this.getFieldsValue,
      setFieldsValue: this.setFieldsValue,
      getFieldValue: this.getFieldValue,
      setFieldValue: this.setFieldValue,
      validate: this.validate,
      registerEneity: this.registerEneity,
      setCallbacks: this.setCallbascks,
      submit: this.submit,
    };
  };
}

const useForm = (form) => {
  const formInstance = React.useRef();

  if (!formInstance.current) {
    if (form) {
      formInstance.current = form;
    } else {
      const store = new formStore();
      formInstance.current = store.getForm();
    }
  }

  return [formInstance.current];
};

export { useForm };

你以为这样就完了吗,不不不。接下来我们看简单看一看antd3中Form组件的实现

antd3中的表单组件

antd3中使用高阶组件的方式实现Form表单,我们简单说一下高阶组件的实现思路,我们使用一个createForm高阶组件,它返回一个类组件,像上面一样我们需要一个地方存储所有的表单值以及创建修改值的方法,我们在createForm里面用this.state存储表单值,并使用getFieldDecorator方法为input组件添加onChange事件,以及valuevaluestate中获取,这样通过setState方法改变值时视图也会更新,当input值改变时通过setState将改变更新到state上面。setFieldsValue、getFieldsValue这些方法很简单就不说了。
代码如下

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
        // 如果是undefind 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()} />;
    }
  };
}

使用代码

import React, {Component} from "react";
// import {createForm} from "rc-form";
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);
    // this.state = {
    //   username: "",
    //   password: ""
    // };
  }

  componentDidMount() {
    this.props.form.setFieldsValue({username: "default"});
  }

  submit = () => {
    const {getFieldsValue, validateFields} = this.props.form;
    // console.log("submit", getFieldsValue()); //sy-log
    validateFields((err, val) => {
      if (err) {
        console.log("err", err); //sy-log
      } else {
        console.log("校验成功", val); //sy-log
      }
    });
  };

  render() {
    console.log("props", this.props); //sy-log
    // const {username, password} = this.state;
    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;
 类似资料: