实现antd中Form、Form.Item组件

2023-11-10

实现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;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实现antd中Form、Form.Item组件 的相关文章

  • Linux下频繁读写文件,导致可用内存减少

    问题现象 Linux下从服务器下载文件时 通过回调函数一直写文件 频繁的进行write操作 导致系统可用内存一直减少 有时候可能会导致程序执行因为内存问题异常 测试代码如下 Copyright C 2019 All rights reser
  • 小程序分包配置

    在pages同级新建subPack文件夹 然后在pages中将分包配置进去 分包加载配置 此配置为小程序的分包加载机制 subPackages root subPack 子包的根目录 pages 这里的配置路径和pages里的一样 path
  • 【Linux下MySQL的初始化和配置】

    Linux下MYSQL的初始化和配置 一 初始准备 一 服务初始化 二 启动MYSQL 三 MYSQL登录 二 设置远程登录 一 确认网络 二 关闭防火墙 三 Linux下修改配置 一 初始准备 先去官网把需要的MYSQL版本下载并安装好
  • Spring Bean的生命周期(非常详细)

    Spring作为当前Java最流行 最强大的轻量级框架 受到了程序员的热烈欢迎 准确的了解Spring Bean的生命周期是非常必要的 我们通常使用ApplicationContext作为Spring容器 这里 我们讲的也是 Applica
  • (2021年)is not a supported wheel on this platform解决方案

    今天安装环境时碰到了这个问题 一脸懵逼 经过查阅得知是因为某个 whl文件和python的版本不兼容导致了这个问题 我看了很多人的回答 把别人的成果总结起来发现其实解决这个问题也很简单 但是这里不保证可以适用所有人的环境 造成这个问题的原因

随机推荐

  • java 数据库断连_Java + Tomcat,正在断开数据库连接?

    我有一个tomcat实例设置 但是context xml在一段时间不活动之后 我在其中配置的数据库连接不断消失 当我检查日志时 出现以下错误 com mysql jdbc exceptions jdbc4 CommunicationsExc
  • Java编写的美食网站 美食系统 功能齐全、界面漂亮 下载即可以运行

    8月份由于公司的事情太多 基本上没有更新博客信息 今天稍微空了点 继续为为大家介绍Java web项目 今天要介绍的是一个Java web编写的美食网站 美食系统 美食网站分两类用户 普通用户和系统管理员 普通用户具备的主要功能包括 登陆
  • git clone错误记录

    Unable to negotiate with XXXportXXX no matching host key type found Their offer ssh rsa 修改 ssh config Host PubkeyAccepte
  • 2022 华为杯数模研赛E题原创python代码

    每小问都会有对应的代码 并给出部分处理后的数据 可视化图等 已更新好前三问 注 目前市面上的资料都已经看过了 小p的那个Python代码 个人认为过分想要出的速度快 实际质量不太好 直接参考很难获奖 全程无脑机器学习 这种代码我可以写一筐
  • 成员模板函数

    Member Function Templates 11 04 2016 2 minutes to read Contributors all The term member template refers to both member f
  • 在 K8S 中快速部署 Redis Cluster & Redisinsight

    Redis Cluster 部署 使用 Bitnami helm chart 在 K8S redis 命名空间中一键部署 Redis cluster helm repo add bitnami https charts bitnami co
  • RS-485详解(一)

    RS 485是美国电子工业协会 EIA 在1983年批准了一个新的平衡传输标准 balanced transmission standard EIA一开始将RS Recommended Standard 做为标准的前缀 不过后来为了便于识别
  • sqli--labs 进阶篇 23_24关

    第二十三关 基于单引号的过滤字符注入 直接爆点 测试是否报错 从下面的提示信息 可以知道是单引号 id 代码审计 进一步确定自己的推论 源码中 存在过滤掉一些注释语句 id 1 推测结构 参数XX limit 0 1 利用回显确定下自己的推
  • 【Linux 速查手册】基于CentOS的Linux 文件结构以及在搭建LAMP环境

    文章目录 LAMP Linux 主要文件结构 Apache 作为 Web 服务器的文件结构 在Centos 中 home目录和 目录的区别 写在最后 LAMP LAMP是指使用 Linux Apache MySql PHP 搭建而成的网站
  • 2018最有前景的编程语言, 你选对了吗?

    对于程序员来说 世间最可怕的事情 莫过于 刚刚学过的编程语言就已经过时 对于求职者来说 了解受欢迎的编程语言及趋势 无论是对找工作 还是规划将来的职业发展 都有很大的好处 基于各种可信来源的数据统计 我对2018年初IT行业编程语言的状态
  • Debian 10驱动Broadcom 无线网卡

    用lspci命令查询无线网卡品牌 运行下面代码后 重启即可 apt get install linux image uname r sed s linux headers uname r sed s broadcom sta dkms
  • QT处理日志文件

    由于实际生产需要 软件系统的运行 会产生大量的日志文件 有时候一天就能产生超过百万条log记录 那么为了能够处理日志文件 查询并且找到我们想要的报错信息 因此不得不考虑怎么实现 打开大日志文件的可行方法 在这里我采用的是内存映射的方式去读取
  • 深入理解神经网络:使用Python实现简单的前馈神经网络

    在本文中 我们将深入理解前馈神经网络的原理 并使用Python编程实现一个简单的前馈神经网络 我们将使用NumPy库来处理矩阵运算 并将逐步解释神经网络的各个组成部分 包括神经元 激活函数 前向传播 反向传播和梯度下降 最后 我们将以一个简
  • k8s-基础入门

    目录 一 k8s的特性 二 kubernetes的基本组件 1 Pod 最小的资源单位 1 1 Pod的两个分类 2 资源清单 3 Pod 控制器 维护Pod状态 期望值 4 服务发现 Service同一个访问入口 5 存储服务分类 6 调
  • linux rpm软件包管理,linux之rpm软件包管理

    1 RPM包的命名规则 例如 httpd 2 2 15 15 el6 centos 1 i686 rpm httpd 软件包名 2 2 15 软件版本 15 发行次数 e16 centos 适合的linux平台 i686 适合的硬件平台 r
  • Angular之ngModel报错:angular-can‘t-bind-to-‘ngModel‘---

    做双向绑定时 如果遇见Angular Can t bind to ngModel since it isn t a known property of input 问题 这是由于没有在当前组件所属的Module中引用FormModule 注
  • 操作系统-进程概念与进程控制块

    进程 在学习操作系统时 对于进程我们经常能看到如下几个定义 一个正在执行的程序 一个正在计算机上执行的程序实例 能分配给处理器并由处理器执行的实体 由一组执行的指令 一个当前状态和一组相关的系统资源表征的活动单元 以上定义都是很抽象的 将进
  • Papers with Code一个查找论文和对应代码的神器

    0x01 Papers with Code是什么 Papers with Code 是一个包含机器学习论文及其代码实现的网站 大多数论文都是有GitHub代码的 这个网站很牛逼的地方就是对机器学习方向做了任务分类 检索对应的论文 数据 代码
  • Shell 编程:探索 Shell 的基本概念与用法

    目录 Shell 简介 Shell 脚本 Shell 脚本运行 Shell 变量 1 创建变量和赋值 2 引用变量 3 修改变量的值 4 只读变量 5 删除变量 6 环境变量 Shell 字符串操作 1 拼接字符串 2 字符串长度 3 字符
  • 实现antd中Form、Form.Item组件

    实现antd中Form Form Item组件 初始化项目 使用create react app初始化项目后创建MyRcFieldForm文件 代码如下 import React Component useEffect from react