antd的form表单submit提交不⽣效_深⼊学习并⼿写
ReactAntDesign4。。。
前⾔
最近有⼀个⾮常复杂的表单需求,可能需要对表单做“任何事情”,现有的 UI 组件库选⽤的是 Ant Design 简称 antd 。它的 Form 表单已经帮我们把“表单项校验”、“表单项错误信息”等常见操作全部封装好了。使⽤起来⾮常便捷。翻看了 antd Form 源码发现其核⼼能⼒都是通过 rc-field-form 库,提供出来的。因此阅读它的源码将是作者项⽬开始前必须要做的。
本⽂将模拟 rc-field-form 库,⼿写⼀个“学习版” ,深⼊学习其思想。
如果本⽂对你有所帮助,请点个? 吧!
⼯程搭建
rc-field-form 使⽤的是 Dumi 和 father-build 对组件库进⾏打包,为了保持⼀致,作者也将使⽤这两个⼯具来完成项⽬。
Dumi
dumi 中⽂发⾳嘟⽶,是⼀款为组件开发场景⽽⽣的⽂档⼯具,与 father-builder ⼀起为开发者提供⼀站式的组件开发体验, father-builder 负责构建,⽽ dumi 负责组件开发及组件⽂档⽣成。
father-build
father-build 属于 father (集⽂档与组件打包⼀体的库)的⼀部分,专注于组件打包。
脚⼿架创建项⽬
使⽤ @umijs/create-dumi-lib 来初始化项⽬。这个脚⼿架整合了上⾯提及的两个⼯具。
mkdir lion-form // 创建lion-form⽂件夹
cd lion-form // 进⼊⽂件夹
npm init -y // 初始化 package.json
npx @umijs/create-dumi-lib // 初始化整体项⽬结构
项⽬结构说明
├──README.md // ⽂档说明
├──node_modules // 依赖包⽂件夹
形容溪水的词语├──package.json // npm 包管理
├──.editorconfig // 编辑器风格统⼀配置⽂件
├──.fatherrc.ts // 打包配置
├──.umirc.ts // ⽂档配置
├──.prettierrc // ⽂本格式化配置
├──tsconfig.json // ts 配置
└──docs // 仓库公共⽂档
└──index.md // 组件库⽂档⾸页
└──src
└──index.js // 组件库⼊⼝⽂件
启动项⽬
npm start 或 yarn start
集⽂档,打包为⼀体的组件库就这样快速的搭建完成了。下⾯就让我们来⼿写⼀个 rc-field-form 吧。
完整代码地址
源码编写
rc-field-form
对于经常使⽤ react 开发的同学来说, antd 应该都不会陌⽣。开发中经常遇到的表单⼤多会使⽤ antd 中的 Form 系列组件完成,⽽ rc-field-form ⼜是 antd Form 的重要组成部分,或者说 antd Form 是对 rc-field-form 的进⼀步的封装。
想要学习它的源码,⾸先还是得知道如何使⽤它,不然难以理解源码的⼀些深层次的含义。
简单的⽰例
⾸先来实现如下图所⽰的表单,类似于我们写过的登录注册页⾯。
代码⽰例:
import React, { Component, uEffect} from 'react'
import Form, { Field } from 'rc-field-form'
import Input from './Input'
// name 字段校验规则
const nameRules = {required: true, message: '请输⼊姓名!'}
// password 字段校验规则
const passwordRules = {required: true, message: '请输⼊密码!'}
export default function FieldForm(props) {
// 获取 form 实例
const [form] = Form.uForm()
// 提交表单时触发
const onFinish = (val) => {
console.log('onFinish', val)
}
// 提交表单失败时触发
const onFinishFailed = (val) => {
console.log('onFinishFailed', val)
}
/
/ 组件初始化时触发,它是React原⽣Hook
uEffect(() => {
form.tFieldsValue({urname: 'lion'})
}, [])
return (
FieldForm</h3>>
</Field>>
</Field>Submitbutton>
电饭煲腊味饭
</Form>div>
)
}// input简单封装const Input = (props) => {const { value,...restProps } = props;return ;
};
这种写法还是⾮常便捷的,不再需要像 antd3 ⼀样使⽤⾼阶函数包裹⼀层。⽽是直接通过 Form.uForm() 获取到 formInstance 实
例, formInstance 实例⾝上承载了表单需要的所有数据及⽅法。
通过 form.tFieldsValue({urname: 'lion'}) 这段代码就不难发现,可以通过 form 去⼿动设置 urname 的初始值。也可以理解成所有的表单项都被 formInstance 实例接管了,可以使⽤ formInstance 实例做到任何操作表单项的事情。 formInstance 实例也是整个库的核⼼。
基础框架搭建
通过对 rc-field-form 源码的学习,我们先来搭建⼀个基础框架。
uForm
通过 Form.uForm() 获取 formInstance 实例;
formInstance 实例对外提供了全局的⽅法如 tFieldsValue 、 getFieldsValue ;
通过 context 让全局可以共享 formInstance 实例。
src/uForm.tsx
import React , {uRef} from "react";
class FormStore {
// stroe ⽤来存储表单数据,它的格式:{"urname": "lion"}
private store: any = {};
// ⽤来存储每个 Field 的实例数据,因此在store中可以通过 fieldEntities 来访问到每个表单项
private fieldEntities: any = [];
// 表单项注册到 fieldEntities
// 表单项注册到 fieldEntities
registerField = (entity:any)=>{
this.fieldEntities.push(entity)
return () => {
this.fieldEntities = this.fieldEntities.filter((item:any) => item !== entity)
delete this.store[entity.props.name]
}
}
// 获取单个字段值
getFieldValue = (name:string) => {
return this.store[name]
}
// 获取所有字段值
getFieldsValue = () => {
return this.store
}
// 设置字段的值
tFieldsValue = (newStore:any) => {
// 更新store的值
this.store = {
...this.store,
...newStore,
}
// 通过 fieldEntities 获取到所有表单项,然后遍历去调⽤表单项的 onStoreChange ⽅法更新表单项
转球技巧
黄豆炖排骨的做法
this.fieldEntities.forEach((entity:any) => {
const { name } = entity.props
Object.keys(newStore).forEach(key => {
if (key === name) {
}
})
})
}早泄恢复方法
// 提交数据,这⾥只简单的打印了store中的数据。
submit = ()=>{
console.FieldsValue());红楼梦的好句
}
// 提供FormStore实例⽅法
getForm = (): any => ({
getFieldValue: FieldValue,
getFieldsValue: FieldsValue,
孩子的微笑tFieldsValue: this.tFieldsValue,
registerField: isterField,
submit: this.submit,
});
}
/
/ 创建单例formStore
export default function uForm(form:any) {
const formRef = uRef();
if (!formRef.current) {
if (form) {
formRef.current = form;
} el {
const formStore = new FormStore();
formRef.current = Form() as any;
}
}
return [formRef.current]
}
其中 FormStore 是⽤来存储全局数据和⽅法的。 uForm 是对外暴露 FormStore 实例的。从 uForm 的实现可以看出,借助 uRef 实现了 FormStore 实例的单例模式。
FieldContext
定义了全局 context 。
import * as React from 'react';
const warningFunc: any = () => {
console.log("warning");
};
const Context = ateContext({
getFieldValue: warningFunc,
getFieldsValue: warningFunc,
tFieldsValue: warningFunc,
registerField: warningFunc,
submit: warningFunc,
});
export default Context;
Form 组件
传递 FieldContext;
代理包括拦截处理 submit 事件;
渲染⼦节点。
src/Form.tsx
import React from "react";
import uForm from "./uForm";
import FieldContext from './FieldContext';
export default function Form(props:any) {
const {form, children, ...restProps} = props;
const [formInstance] = uForm(form) as any;
return {...restProps}
onSubmit={(event: React.FormEvent) => {
event.preventDefault();
event.stopPropagation();// 调⽤了formInstance 提供的submit⽅法
formInstance.submit();
}}
>
{/* formInstance 当做全局的 context 传递下去 */}{children}</FieldContext.Provider>form> }
Field 组件
把⾃⼰注册到 FormStore 中;
拦截⼦元素为其添加 value 以及 onChange 属性。
src/Field.tsx