|
1 |
| -复杂表单 |
2 | 1 |
|
3 |
| -### 背景 |
4 |
| -> 当我们在做后台管理系统时,经常会遇到非常复杂的表单: |
| 2 | +## 背景 |
| 3 | + 当我们在做后台管理系统时,经常会遇到非常复杂的表单: |
5 | 4 | > + 表单项非常多
|
6 |
| -> + 在各种表单类型下,显示的表单项不同 |
| 5 | +> + 在各种表单类型下,显示不同的表单项 |
7 | 6 | > + 在某些条件下,某些表单项会关闭验证
|
8 |
| -> + 在这种错综复杂的情况下,完成表单的验证和提交 |
9 | 7 | > + 每个表单项还会有其他自定义逻辑,比如**输入框可以插入模板变量、输入字符数量显示、图片上传并显示、富文本**。。。
|
| 8 | +> + 在这种错综复杂的情况下,完成表单的验证和提交 |
| 9 | +> + [可以查看具体例子](https://kevinlij.github.io/Verification-of-complex-forms/):例子中省略了很多琐碎的功能,只保留整体的复杂表单框架,用于展示解决方案 |
10 | 10 |
|
11 |
| -### 普通的方式 |
12 |
| -> 所有的显示隐藏逻辑、数据获取提交逻辑 放在一起 |
| 11 | +## 方案1: 在一个`vue`文件中 |
| 12 | + 所有的表单项显示隐藏、验证、数据获取、提交、自定义等逻辑放在一起 |
13 | 13 | > + 根据表单类型,使用`v-if/v-show`处理表单项显示隐藏
|
14 |
| -> + 在表单验证逻辑中加入条件,判断是否验证 |
15 |
| -> + 根据表单类型,获取不同的数据,并提交 |
| 14 | +> + 在`elementui`自定义验证中,根据表单类型,判断表单项是否验证 |
| 15 | +> + 根据表单类型,获取不同的数据,并提交到不同的接口 |
16 | 16 | > + 其余所有的自定义逻辑
|
17 | 17 |
|
18 |
| -##### 缺点 |
| 18 | +#### 缺点 |
19 | 19 | + 乱
|
20 | 20 | + 乱
|
21 | 21 | + 还是乱
|
22 | 22 | + 一个`vue`文件,轻轻松松上`2000`行
|
| 23 | ++ 在我尝试加入一种新的表单类型时,我发现我已经无。从。下。手。 |
23 | 24 |
|
24 | 25 |
|
25 |
| -### 分离组件 |
26 |
| -> 其实很容易想到不同的表单类型,分离出不同的子表单。但我在实践时还是遇到了很多问题,并总结出一套解决方案 |
| 26 | +## 方案2:分离组件 |
| 27 | +> 其实很容易想到**根据不同的表单类型,分离出多个相应类型的子表单**。但我在实践时还是遇到了很多问题:**父子表单验证、整体提交数据的获取**等等,并总结出一套解决方案: |
27 | 28 |
|
28 |
| -##### 1. 子组件 |
| 29 | +### 1. 子组件 |
| 30 | +所有的子组件中都需要包含两个方法`validate`、`getData`供父组件调用。 |
29 | 31 |
|
30 |
| -`validate`方法:返回`elementUI`表单验证的`promise`对象。用于父组件调用,验证自己组件的表单项 |
| 32 | +#### (1) **`validate`方法** |
| 33 | +用于验证本身组件的表单项,并返回一个`promise`对象 |
31 | 34 | ```js
|
32 | 35 | vaildate() {
|
| 36 | + // 返回`elementUI`表单验证的结果(为`promise`对象) |
33 | 37 | return this.$refs["ruleForm"].validate();
|
34 | 38 | },
|
35 | 39 |
|
36 | 40 | ```
|
37 |
| -`getData`方法:用于父组件调用,提取子组件中的数据 |
| 41 | +#### (2) **`getData`方法** |
| 42 | +提供子组件中的数据 |
38 | 43 | ```js
|
39 | 44 | getData() {
|
| 45 | + // 返回子组件的form |
40 | 46 | return this.ruleForm;
|
41 | 47 | },
|
42 | 48 | ```
|
43 | 49 |
|
44 |
| -##### 2. 父组件 |
45 |
| -`submit`方法:父子组件表单验证、获取整体数据、根据不同类型发送不同接口。 |
| 50 | +### 2. 父组件 |
46 | 51 |
|
47 |
| -+ 使用[策略模式](https://www.cnblogs.com/xiaohuochai/p/8029651.html)获取当前类型表单的`ref`和提交函数。省略了大量的`if-else`判断。 |
| 52 | +#### (1) [策略模式](https://www.cnblogs.com/xiaohuochai/p/8029651.html) |
| 53 | +使用策略模式存储并获取**子表单的`ref`**(用于获取子表单的方法)和**提交函数** 。省略了大量的`if-else`判断。 |
48 | 54 |
|
49 | 55 | ```js
|
50 | 56 | data:{
|
51 |
| - // ref名称 |
52 |
| - typeMap: { |
| 57 | + // type和ref名称的映射 |
| 58 | + typeRefMap: { |
53 | 59 | 1: "message",
|
54 | 60 | 2: "mail",
|
55 | 61 | 3: "apppush"
|
56 | 62 | },
|
57 |
| - // 模拟的不同类型表单的提交,url会不同 |
58 |
| - fakeSubmit: { |
| 63 | + // type和提交函数的映射。不同类型,接口可能不同 |
| 64 | + typeSubmitMap: { |
59 | 65 | 1: data => alert(`短信模板创建成功${JSON.stringify(data)}`),
|
60 | 66 | 2: data => alert(`邮件模板创建成功${JSON.stringify(data)}`),
|
61 | 67 | 3: data => alert(`push模板创建成功${JSON.stringify(data)}`)
|
62 | 68 | },
|
63 | 69 | }
|
64 | 70 | ```
|
65 |
| -+ `elementUI`表单验证`validate`方法可以返回`promise`结果,可以利用`promise`的特性来处理父子表单的验证。比如[`then`函数可以返回另一个`promise`对象](https://juejin.im/post/5cc17448f265da0379417cfc#heading-0)、[`catch`可以获取它以上所有`then`的`reject`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-1)、[`Promise.all`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-6)。 |
66 |
| - + 父表单验证通过才会验证子表单,存在先后顺序 |
| 71 | + |
| 72 | +#### (2) **`submit`方法** |
| 73 | +用于**父子组件表单验证、获取整体数据、调用当前类型提交函数提交数据** |
| 74 | +> **因为`elementUI`表单验证的`validate`方法可以返回`promise`结果**,可以利用`promise`的特性来处理父子表单的验证。 |
| 75 | +比如[`then`函数可以返回另一个`promise`对象](https://juejin.im/post/5cc17448f265da0379417cfc#heading-0)、[`catch`可以获取它以上所有`then`的`reject`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-1)、[`Promise.all`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-6)。 |
| 76 | + |
| 77 | ++ 父表单验证通过才会验证子表单,存在先后顺序 |
67 | 78 | ```js
|
68 | 79 | // 父表单验证通过才会验证子表单,存在先后顺序
|
69 | 80 | submitForm() {
|
70 |
| - const templateType = this.typeMap[this.indexForm.type]; |
| 81 | + const templateType = this.typeRefMap[this.indexForm.type]; |
71 | 82 | this.$refs["indexForm"]
|
72 | 83 | .validate()
|
73 | 84 | .then(res => {
|
74 |
| - // 父表单验证成功后,验证子表单 |
| 85 | + // 父表单验证成功后,验证子表单 |
75 | 86 | return this.$refs[templateType].vaildate();
|
76 | 87 | })
|
77 | 88 | .then(res => {
|
| 89 | + // 全部验证通过 |
78 | 90 | // 获取整体数据
|
79 | 91 | const reqData = {
|
80 | 92 | // 获取子组件数据
|
81 | 93 | ...this.$refs[templateType].getData(),
|
82 | 94 | ...this.indexForm
|
83 | 95 | };
|
84 |
| - this.fakeSubmit[this.indexForm.type](reqData); |
| 96 | + // 获取当前表单类型的提交函数,并提交 |
| 97 | + this.typeSubmitMap[this.indexForm.type](reqData); |
85 | 98 | })
|
86 | 99 | .catch(err => {
|
87 | 100 | console.log(err);
|
88 | 101 | });
|
89 | 102 | },
|
90 | 103 | ```
|
91 |
| - + 父表单,子表单一起验证 |
92 |
| - ```js |
93 |
| - submitForm1() { |
94 |
| - const templateType = this.typeMap[this.indexForm.type]; |
95 |
| - const validate1 = this.$refs["indexForm"].validate(); |
96 |
| - const validate2 = this.$refs[templateType].vaildate(); |
97 |
| - 父子表单一起验证 |
98 |
| - Promise.all([validate1, validate2]) |
99 |
| - .then(res => { |
100 |
| - // 都通过时,发送请求 |
101 |
| - const reqData = { |
102 |
| - ...this.$refs[templateType].getData(), |
103 |
| - ...this.indexForm |
104 |
| - }; |
105 |
| - this.fakeSubmit[this.indexForm.type](reqData); |
106 |
| - }) |
107 |
| - .catch(err => { |
108 |
| - console.log(err); |
109 |
| - }); |
110 |
| - }, |
111 |
| - ``` |
112 |
| -> 总结:很多项目我都遇到这种复杂的表单,也用了很多种解决方案,在此总结出了一种比较整洁简便的方案。当然还有其他很多方案,比如**可以把数据提交的方法放在每一个子组件中,公共的表单项数据通过`props`传递给子组件用于提交**。有其他更加简洁的方案,欢迎评论,或者`github`上提`issue` |
113 |
| -> |
114 |
| -> 题外话: 看了[前端架构师亲述:前端工程师成长之路的 N 问 及 回答](https://juejin.im/post/5d0ba00af265da1bc7524043#heading-3)中的几个回答后,对我有很大的启发。在对自己的技术方向、前景迷茫时,或者在埋怨自己的项目太low时,或者埋怨自己每天在做重复工作时,或者每天对层出不穷的新技术焦头烂额时,**不妨认真的审视下自己的项目,每天重复的工作,是不是可以自己造轮子了;项目太low,是不是可以平滑过渡到新技术,提高开发效率。学再多的新技术,最终也会回归并实践到项目中,所以做好当前很重要。从工作和项目的痛点出发,你会一边探索一边进步,一步步走向大神级别** |
| 104 | ++ 父表单,子表单一起验证 |
| 105 | + ```js |
| 106 | + submitForm1() { |
| 107 | + const templateType = this.typeRefMap[this.indexForm.type]; |
| 108 | + const validate1 = this.$refs["indexForm"].validate(); |
| 109 | + const validate2 = this.$refs[templateType].vaildate(); |
| 110 | + 父子表单一起验证 |
| 111 | + Promise.all([validate1, validate2]) |
| 112 | + .then(res => { |
| 113 | + // 都通过时,发送请求 |
| 114 | + const reqData = { |
| 115 | + ...this.$refs[templateType].getData(), |
| 116 | + ...this.indexForm |
| 117 | + }; |
| 118 | + this.typeSubmitMap[this.indexForm.type](reqData); |
| 119 | + }) |
| 120 | + .catch(err => { |
| 121 | + console.log(err); |
| 122 | + }); |
| 123 | + }, |
| 124 | + ``` |
| 125 | + |
| 126 | +#### 查看[在线项目](https://kevinlij.github.io/Verification-of-complex-forms/)、[项目github](https://github.com/kevinLiJ/Verification-of-complex-forms)和[组件代码](https://github.com/kevinLiJ/Verification-of-complex-forms/tree/master/src/components) |
| 127 | + |
| 128 | +> 总结:很多项目我都遇到这种复杂的表单,也用了很多种解决方案,在此总结出了一种比较整洁简便的方案。当然还有其他很多方案,比如**可以把数据提交的方法放在每一个子组件中,公共的表单项数据通过`props`传递给子组件用于提交**。有其他更加简洁的方案,欢迎评论,或者[`github`上提`issue`](https://github.com/kevinLiJ/Verification-of-complex-forms/issues) |
| 129 | + |
| 130 | + |
| 131 | +> 题外话: 看了[前端架构师亲述:前端工程师成长之路的 N 问 及 回答](https://juejin.im/post/5d0ba00af265da1bc7524043#heading-3)中的几个回答后,对我有很大的启发。在**对自己的技术方向、前景迷茫时、或者在埋怨自己的项目太low时、或者埋怨自己每天在做重复工作时、或者每天对层出不穷的新技术焦头烂额时**,不妨**认真的审视下自己的项目**, |
| 132 | ++ 每天重复的工作,是不是可以自己造轮子了; |
| 133 | ++ 技术栈太low,是不是可以平滑过渡到新技术,提高开发效率; |
| 134 | ++ 学再多的新技术,最终也会回归并实践到项目中。 |
| 135 | + |
| 136 | +从**工作流程和项目的痛点**出发,**你会在实践、总结并解决实际问题中进步的更加迅速**。 |
| 137 | + |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +> 写这篇文章的感受:**把这些东西表达出来的难度 `>>` 文章本身所包含的技术难度** |
0 commit comments