当前位置: 首页 > 科技观察

教你实现一个常用的antd表单组件

时间:2023-03-18 14:29:53 科技观察

1。Form组件解决的问题我们从官网拿了一段Form代码,可以清楚的看到一个简单的表单,主要是为了统一采集和验证组件的值。{console.log('values',values)}}>提交所以它是如何实现统一采集和验证的?原理很简单。只需要监听表单组件的onChange事件获取表单项的值,根据定义的校验规则校验值,生成校验状态和校验信息,然后通过setState驱动视图更新显示组件的值和只是验证信息。2.AntdForm是如何实现的要实现上述方案,需要解决这些问题:如何实时采集组件数据?如何验证组件的数据?如何更新组件的数据?如何跨层级传递表单提交接下来我们就带着这些问题一步步实现3.目录结构1659421573127.jpgsrc/index.tsx用来放测试代码src/components/Form文件夹用来存放FormComponentinformationinterface.ts用于存储数据类型useForm存储数据仓库内容index.tsxexportsForm组件相关FiledContext存储FormglobalcontextForm外部组件Filed内部组件4.数据类型定义本项目使用ts搭建,所以我们首先定义数据类型;//src/components/Form/interface.ts导出类型StoreValue=any;导出类型Store=Record;导出类型NamePath=字符串|数字;exportinterfaceCallbacks{onFinish?:(values:Values)=>void;}exportinterfaceFormInstance{getFieldValue:(name:NamePath)=>StoreValue;提交:()=>无效;getFieldsValue:()=>值;setFieldsValue:(newStore:Store)=>void;setCallbacks:(callbacks:Callbacks)=>void;}5.数据仓库因为我们的表单必须是各种不同的数据项,比如input,checkbox,radio等等,如果这些组件每个都要管理自己的值。组件的数据管理太杂乱了,我们没有必要这么做。如何统一管理?其实就是我们自己定义一个数据仓库,在顶层把定义好的仓库操作和数据提供给下层。这样,我们就可以对每一层的数据仓库进行操作了。数据仓库的定义,说白了就是一些读和取操作,所有的操作都定义在一个文件中,代码如下://src/components/Form/useForm.tsimport{useRef}from"react";从"./interface"导入类型{Store,NamePath,Callbacks,FormInstance};classFormStore{privatestore:Store={};私有回调:Callbacks={};getFieldsValue=()=>{return{...this.store};};getFieldValue=(name:NamePath)=>{returnthis.store[name];};setFieldsValue=(newStore:Store)=>{this.store={...this.store,...newStore,};};setCallbacks=(callbacks:Callbacks)=>{this.callbacks={...this.callbacks,...callbacks};};提交=()=>{const{onFinish}=this.callbacks;如果(onFinish){onFinish(this.getFieldsValue());}};getForm=():FormInstance=>{return{getFieldsValue:this.getFieldsValue,getFieldValue:this.getFieldValue,setFieldsValue:this.setFieldsValue,提交:this.提交,setCallbacks:this.setCallbacks,};};}什么时候当然数据仓库不能就这么放着,我们需要把里面的内容暴露出来这里使用ref来保存,保证组件初始渲染和更新阶段使用同一个数据仓库实例;//src/components/Form/useForm.tsexportdefaultfunctionuseForm(form?:FormInstance):[FormInstance]{constformRef=useRef();如果(!formRef.current){如果(表单){formRef.current=表单;}else{constformStore=newFormStore();formRef!.current=formStore.getForm();}}return[formRef.current];}6.组件数据的实时采集我们先来定义form的结构,如下代码所示://src/index.tsximportReactfrom"react";importForm,{Field}from"./components/Form";constindex:React.FC=()=>{return({console.log("values",values);}}>提交);};导出默认t指数;数据仓库定义好之后,我们必须想办法让每一层都有消费它的能力,所以这里我们在顶层使用context来跨层传递数据,通过顶层的形式将数据仓库向下传递。代码如下://src/components/Form/Form.tsximportReactfrom"react";importFieldContextfrom"./FieldContext";importuseFormfrom"./useForm";importtype{Callbacks,FormInstance}from"./interface";interfaceFormProps{form?:FormInstance;onFinish?:Callbacks["onFinish"];}constForm:React.FC=(props)=>{const{children,onFinish,form}=道具;const[formInstance]=useForm(form);formInstance.setCallbacks({onFinish});返回({e.preventDefault();formInstance.submit();}}>{children});};导出默认表单;子组件用于存储和检索操作。这里有个问题,为什么不直接在input和radio组件上加上access操作,还要包裹一层Field(antd官方的Form.Item)?这是因为它需要基于它的基本能力。扩展一些功能。//src/components/Form/Field.tsximportReact,{ChangeEvent}from"react";importFieldContextfrom"./FieldContext";importtype{NamePath}from"./interface";constField:React.FC<{名称:NamePath}>=(props)=>{const{getFieldValue,setFieldsValue}=React.useContext(FieldContext);const{children,name}=props;constgetControlled=()=>{return{value:getFieldValue&&getFieldValue(name),onChange:(e:ChangeEvent)=>{constnewValue=e?.target?.value;setFieldsValue?.({[name]:newValue});},};};返回反应。cloneElement(childrenasReact.ReactElement,getControlled());};exportdefaultField;这样,我们就完成了数据采集和存储的功能。很简单,我们试试onFinish操作吧!接下来,我们继续完善其他功能。7.改进组件渲染让我们修改Form代码并添加一个默认值://src/index.tsximportReact,{useEffect}from"react";importForm,{Field,useForm}from"./components/Form";constindex:React.FC=()=>{const[form]=useForm();//新增代码useEffect(()=>{form.setFieldsValue({username:"default"});},[]);return(//...省略...);};导出默认索引;看一下页面,发现我们设置的默认值并没有显示在表单中,但是我们提交的时候还是可以打印出来的如果有数据输出,证明我们的数据已经存储到store中了,但是尚未渲染到组件中。接下来我们要做的工作就是根据store的变化完成组件表单的响应功能。我们在useForm中添加订阅和退订功能代码;//订阅和取消订阅registerFieldEntities=(entity:FieldEntity)=>{this.fieldEntities.push(entity);return()=>{this.fieldEntities=this.fieldEntities.filter((item)=>item!==entity);const{name}=entity.props;name&&deletethis.store[名称];};};forceUpdate的作用是更新子组件;//src/components/Form/Field.tsx//...省略...const[,forceUpdate]=React.useReducer((x)=>x+1,0);useLayoutEffect(()=>{constunregister=registerFieldEntities&®isterFieldEntities({props,onStoreChange:forceUpdate,});returnunregister;},[]);//...省略...当然,注册是不够的,我们需要在设置值的时候完成响应;//src/components/Form/useForm.tsxsetFieldsValue=(newStore:Store)=>{this.store={...this.store,...newStore,};//新添加的代码//更新Filedthis.fieldEntities.forEach((entity)=>{Object.keys(newStore).forEach((k)=>{if(k===entity.props.name){entity.onStoreChange();}});});};我们看一下效果,发现组件更新了值;8、添加验证函数至此,我们发现提交表单还没有验证函数表单验证通过,则执行onFinish。表单验证的基础是Field的规则。如果表单验证通过,则执行onFinish,如果不通过,则执行onFinishFailed。接下来,我们来实现一个简单的验证。修改代码结构importReact,{useEffect}from"react";importForm,{Field,useForm}from"./components/Form";constnameRules={required:true,message:"Pleaseenteryourname!"};constpassworRules={required:true,message:"请输入您的密码!"};常量索引:React.FC=()=>{const[form]=useForm();useEffect(()=>{form.setFieldsValue({username:"default"});},[]);return({console.log("values",values);}}onFinishFailed={(err)=>{console.log("err",err);}}form={form}>提交);};导出默认索引;添加用于表单验证的validateField方法。注意:本版本的验证只增加了需要的验证。后续朋友可以根据自己的需要继续完善!//src/components/Form/useForm.tsx//...省略...validateField=()=>{consterr:any[]=[];this.fieldEntities.forEach((entity)=>{const{name,rules}=entity.props;constvalue:NamePath=name&&this.getFieldValue(name);letrule=rules?.length&&rules[0];if(rule&&rule.required&&(value===undefined||value==="")){name&&err.push({[name]:rule&&rule.message,value});}});返回错误;};我们只需要提交表单进行判断即可;提交=()=>{const{onFinish,onFinishFailed}=this.callbacks;//调用验证方法consterr=this.validateField();if(err.length===0){onFinish&&onFinish(this.getFieldsValue());}else{onFinishFailed&&onFinishFailed(err);}};密码为空时的效果;账号密码不为空时的效果;为此,我们基本上已经实现了一个AntdForm,但是细节和功能还需要逐步完善。有兴趣的朋友可以继续做!9.总结其实我们在看AntdForm源码的时候你会发现它是基于rc-field-form写的,所以想继续往下写的朋友可以下载rc-field-form的源码,边学边写,这样事半功倍努力征服源代码!本文代码地址:https://github.com/linhexs/vite-react-ts-form

最新推荐
猜你喜欢