这篇文章的主题为我们对 Form 表单在数栈产品中使用之后理解消化的一个过程,通过介绍一些 Form 表单中常用到的方法,来理解部分设计思想,加深我们对技术的追求。主要介绍 Form 表单的创建和 Form表单双向绑定(getFieldDecorator)。

后文中所提到的 Form 表单均为 Antd 3.x 中的 Form 组件,以下简称为 Form 表单。在 Form 表单在数栈的应用(上): 校验篇 中提到,我们生在一个最好的时代,其实是别人造好轮子帮我们做了一些事情,那我们今天看一看,别人的轮子是怎么造的,我们自己能不能实现。 留心过 Antd 的同学可能有印象,Antd 是基于 react-component 组件进行了 UI 封装,文章会以 react-component/form 的代码为主。

一、别人的 Form

1.1 From.create

先查看 createForm.js 文件,该文件主要是对 createBaseForm.js 文件进行了一层封装,并加上一些常用的方法。

import createBaseForm from './createBaseForm';

export const mixin = {
getForm() {
return {
getFieldsValue: this.fieldsStore.getFieldsValue,
getFieldValue: this.fieldsStore.getFieldValue,
...
validateFields: this.validateFields,
resetFields: this.resetFields,
};
},
};

function createForm(options) {
return createBaseForm(options, [mixin]);
}

export default createForm;

接下来查看一下 createBaseForm.js 文件,主要查看该文件中的 createBaseForm 方法,这个方法起到装饰器的作用,在 props 中包装了一个默认为 form 的变量,在这个变量中完成 Form 的所有功能。createBaseForm 的作用是拷贝当前传递来的组件,也就是调用函数将当前组件传递下去作为被包装组件,最终返回一个被包装过的具备新属性的组件。

render() {
const { wrappedComponentRef, ...restProps } = this.props; // eslint-disable-line
const formProps = {
// getForm 方法来自 createForm.js,在 props 中包装一个 formPropName 变量,默认为 form
[formPropName]: this.getForm(),
};
// 获取 form 的实例
if (withRef) {
formProps.ref = 'wrappedComponent';
} else if (wrappedComponentRef) {
formProps.ref = wrappedComponentRef;
}
const props = mapProps.call(this, {
...formProps,
...restProps,
});
return <WrappedComponent {...props} />;
}

装饰器(decorator): 是一种与 相关的语法,主要用来修改类和类方法(类属性),大部分面向对象的编程语言都支持这种语法,比如Java、Python。装饰器可以简单理解为:能对一些 对象 进行修改,然后返回一个被包装过的 对象

综合来看,Form.create(options) 实际上是对我们的业务组件进行了一次封装,进行了 Form 相关属性的初始化,挂载了一些需要使用的方法,并将这些方法添加到 props.form 下。

1.2 getFieldDecorator

<FormItem {...formItemLayout} label="姓 名" >
{getFieldDecorator('name', {
initialValue: userInfo.name,
rules: [
{ required: true, message: '姓名不可为空!' }
]
})(
<Input placeholder="请输入姓名" />
)}
</FormItem>

从上述使用代码和下方实现方法可以看出,getFieldDecorator 是一个柯里化的函数,通过 id 和参数的输入,返回以输入组件为入参加上新属性的一个 Dom 节点,把 option 的valuePropName、getValueProps、initialValue、rules 等各种 props 挂载到输入组件上。

getFieldDecorator(name, fieldOption) {
const props = this.getFieldProps(name, fieldOption);
return fieldElem => {
// We should put field in record if it is rendered
this.renderFields[name] = true;

const fieldMeta = this.fieldsStore.getFieldMeta(name);
const originalProps = fieldElem.props;
fieldMeta.originalProps = originalProps;
fieldMeta.ref = fieldElem.ref;
const decoratedFieldElem = React.cloneElement(fieldElem, {
...props,
// 没有 initialValue 时为 undefined,有则是 initialValue 的值
...this.fieldsStore.getFieldValuePropValue(fieldMeta),
});
return supportRef(fieldElem) ? (
decoratedFieldElem
) : (
<FieldElemWrapper name={name} form={this}>
{decoratedFieldElem}
</FieldElemWrapper>
);
};
}

getFieldDecorator 有以下两个作用,可在 createBaseForm.js 文件的 getFieldPropsgetFieldValuePropValue 方法中分别验证:

  • 在初始化数据字段时将数据字段放到 fieldsStore 中;
  • 挂载 props 到输入组件上时会从 fieldsStore中读取数据字段。

1.3 validateFields

通常使用 validateFields 方法对我们的表单数据进行校验,查看 createBaseForm.js 文件中 validateFields 方法的实现后,发现 validateFields 方法返回一个 Promise 并且拼装 validateFieldsInternal 方法需要的参数。

validateFields(ns, opt, cb) {
const pending = new Promise((resolve, reject) => {
...
this.validateFieldsInternal(..., params, callback);
});
...
return pending;
}

再看 validateFieldsInternal 方法的代码,它会从 fieldsStore 中获取 rules 和数据 fields 的值,校验后将错误信息分别存储到对应的 fieldsStore 中。

import AsyncValidator from 'async-validator';

validateFieldsInternal(
fields,
{ fieldNames, action, options = {} },
callback,
) {
const fieldMeta = this.fieldsStore.getFieldMeta(name);
...
const validator = new AsyncValidator(allRules);
validator.validate(allValues, options, errors => {
if (errors && errors.length) {
errors.forEach(e => {
...
const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
fieldErrors.push(e);
});
}
});
...
this.setFields(nowAllFields);
...
}

总得来说,Form 表单从初始化到表单收集校验经过了以下几个步骤: 1、通过 Form.create 方法初始了一些属性到 props.form 中,供开发者调用; 2、通过 getFieldDecorator 初始化表单的属性和值,达到双向绑定的效果; 3、校验通过,把数据存到 fieldsStore 中;校验不通过,把 error 存到 fieldsStore 中,渲染。

二、自己的 Form

效果和代码可以在 https://stackblitz.com/edit/react-ts-uoj5pj 查看。

2.1 getFieldDecorator

/**
* 实现 getFieldDecorator 方法
* 初始化时将 initialValue 赋值给输入框的 value
* 输入框变化时可以拿到 value
*/
const getFieldDecorator = (key: string, options: any) => {
// 判断是否第一次赋值,避免死循环
const first = Object.keys(formData).indexOf(key) === -1;

if (options.rules) {
rules[key] = [...options.rules];
}
if (first && options.initialValue) {
setFormData({ ...formData, [key]: options.initialValue });
}

return (formItem) => {
if (errObj[key]) {
formItem = {
...formItem,
props: { ...formItem.props, className: 'input err' },
};
}
return (
<div className="form-item">
{React.cloneElement(formItem, {
name: key,
value: formData[key] || '',
onChange: (e: any) => {
// 输入框值变化时去除错误提示
setErrObj({ ...errObj, [key]: '' });
setFormData({ ...formData, [key]: e.target.value });
},
onBlur: () => {
// 当前默认 blur 时进行校验
validateFields();
},
})}
<div className="err-text">{errObj[key] || ' '}</div>
</div>
);
};
};

2.2 validateFields

// 绑定校验方法
const validateFields = (cb?: any) => {
let errObjTemp = {};
Object.keys(rules).forEach((key) => {
rules[key].forEach((rule) => {
if (rule?.required && (!formData[key] || formData[key].trim() === '')) {
errObjTemp = {
...errObjTemp,
[key]: rule?.message || `${key}为必填项!`,
};
setErrObj(errObjTemp);
}
});
});
cb && cb(Object.keys(errObjTemp).length ? errObjTemp : undefined, formData);
};

2.3 createForm

const createForm = (FormFunc) => (props) => {
const [formData, setFormData] = useState({});
const [errObj, setErrObj] = useState({});
const rules = {}; ... // 将自定义方法挂载到 props 上
return FormFunc({ ...props, getFieldDecorator, validateFields });
};

Form 表单在数栈的应用(下):深入篇的更多相关文章

  1. Jquery学习笔记:操作form表单元素之一(文本框和下拉框)

    一.概述 在web页面开发中,经常需要获取和设置表单元素的值(如文本框中的内容),特别是在ajax应用中,更是常态.本文系统的介绍下如何操作. 同操作其它html元素一样,操作的过程差不多. 第一步, ...

  2. ExtJS4为form表单必填项添加红色*标识

    通常情况下,ExtJS4的form表单必填项在输入状态下会有特殊提示,非输入状态下,显示却和其他项没有任何区别.为使必填项更加容易区分,我们需要根据allowBlank的属性值,为form表单中的必填 ...

  3. SpringMVC.入门篇《二》form表单

    SpringMVC.入门篇<二>form表单 项目工程结构: 在<springmvc入门篇一.HelloWorld>基础上继续添加代码,新增:FormController.ja ...

  4. 巨蟒python全栈开发django11:ajax&&form表单上传文件contentType

    回顾: 什么是异步? 可以开出一个线程,我发出请求,不用等待返回,可以做其他事情. 什么是同步? 同步就是,我发送出了一个请求,需要等待返回给我信息,我才可以操作其他事情. 局部刷新是什么? 通过jq ...

  5. PHP全栈开发(四): HTML 学习(3. form 表单)

    form 表单标签 它表的是一个区域,而非是一个具体的某个元素,它也是作为一个容器的存在. 表单域主要是允许用户在表单域中输入内容,比如文本框,下拉列表,单选框,复选框,等等. <!DOCTYP ...

  6. HTML table、form表单标签的介绍

    1. <table>标签 1.1说明: 在HTML 中定义表格布局. 1.2格式: <table> <caption></caption> <tr ...

  7. Form表单中的action路径问题,form表单action路径《jsp--->Servlet路劲问题》这个和上一个《jsp--->Servlet》文章有关

    Form表单中的action路径问题,form表单action路径 热度5 评论 50 www.BkJia.Com  网友分享于:  2014-08-14 08:08:01     浏览数44525次 ...

  8. [原创]java WEB学习笔记62:Struts2学习之路--表单标签:form,表单标签的属性,textfield, password, hidden,submit ,textarea ,checkbox ,list, listKey 和 listValue 属性,select ,optiongroup ,checkboxlist

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  9. node服务器获取form表单

    搭建好服务器后 (前言,本文只是介绍form表单直接提供给 本页面数据,即在不刷新页面的前提下更改数据) 在public里面添加index.html <!DOCTYPE html> < ...

  10. Django中的Form表单

    Django中已经定义好了form类,可以很容易的使用Django生成一个表单. 一.利用Django生成一个表单: 1.在应用下创建一个forms文件,用于存放form表单.然后在forms中实例华 ...

随机推荐

  1. Unity开发Hololens2—环境配置

    博客地址:https://www.cnblogs.com/zylyehuo/ 配置如下: win11 专业版 Unity2018.4.26f1 / 2019.4.11f1 Hololens2 VS20 ...

  2. 官方的 MCP C# SDK:csharp-sdk

    csharp-sdk 这是 Model Context Protocol(MCP)官方提供的 C# SDK,为 MCP 服务器和客户端提供简单易用的接口, 主要由微软维护.MCP 是由 Claude( ...

  3. 阿里巴巴暑期实习 Java 面经,灵犀互娱一面

    哈希表熟悉吗,可以如何实现? 开散列版本什么时候需要扩容 高并发服务器内的主从reactor模型是如何实现的? 进程 线程 协程 的区别? 如何保证线程安全 ? 了解读写锁吗? 单例模式有了解吗? 可 ...

  4. tomcat非root用户启动

    部署远程服务器时候, 基本都是用root账户登录, 习惯上会直接使用root启动tomcat. 这样其实是有风险的, 黑客获取的权限即容器的权限, 如果容器运行权限就很高,被攻破黑客即可获取很高的权限 ...

  5. 四十种AI编程工具,让你码字如飞

    用过AI编程工具的都知道,这玩意儿虽说还有待完善,但是确实能提高效率啊,真香啊. 是这样的,最近一个同学离职,我临时接手了他的BI数据后台,我一个不会前端的人,使用AI编程工具,发送简单的指令,竟然使 ...

  6. 开源的java内网穿透 - 维基代理(wiki-proxy)

    1.简介 维基代理(wiki-proxy).开源的java内网穿透项目. 技术栈:cdkjFramework(维基框架).JPA.Netty 遵循MIT许可,因此您可以对它进行复制.修改.传播并用于任 ...

  7. 【HUST】代数学|理想的分解习题

    以下内容中,背景知识部分尽数由GPT生成,生成的方式是直接对问题进行提问,存在错误的小节我已经标注,不保证不存在其他错误. 习题部分是GPT生成后,我将看不懂的地方自己重写了一遍的结果.不保证完全正确 ...

  8. 【工具】Typora中主题css修改|看了这篇,一劳永逸

    真正的指南 1. 查看当前的css shift+f12,与一般浏览器调试一样,先打开控制台,查找你需要修改的地方叫什么名字.(也可以点击"视图"-"开发者工具" ...

  9. 基于Jetson Nano与PyTorch的无人机实时目标跟踪系统搭建指南

    引言:边缘计算赋能智能监控 在AIoT时代,将深度学习模型部署到嵌入式设备已成为行业刚需.本文将手把手指导读者在NVIDIA Jetson Nano(4GB版本)开发板上,构建基于YOLOv5+SOR ...

  10. spring boot迁移计划 第Ⅰ章 --chapter 1. rust hyper 结合rust nacos-client开发nacos网关 part ④ nacos-client

    1. toml依赖 nacos_rust_client = "0.3" local_ipaddress = "0.1" ahash = "0.8&qu ...