最近开始维护项目,而后我发现天天的时间经常是花在修改几个小功能上,改了问题有出了另外一个问题,思考哪里不对?,而后这么一天就过去了。。而后我就开始思考标题好让个人时间不老是花在修改上,下面的是我最近的一些总结。html
这个算是面向对象里的思想,在组件里,有不少功能是独立的,好比最多见的发送验证码,确认密码等。把这些逻辑封装成一个或几个函数写在组件里的话,这在组件很小的时候没有什么影响,可是当组件功能比较复杂的时候,就会有些问题:前端
功能分离就是把这些功能抽离出来,写出一个类,而后在组件里引入。async
下面是一个简单的弹窗控制的功能的类和这个类的使用:ide
export class DialogCtrl { isVisible = false; open () { this.isVisible = true; } close () { this.isVisible = false; } }
而后在须要的组件里引入并实例化:函数
DialogCtrl = new this.CommonService.DialogCtrl(); // 是否打开弹窗
在html里能够直接这样用:ui
<nz-modal [nzVisible]="DialogCtrl.isVisible" [nzTitle]="'更新密码'" [nzContent]="modalContent" (nzOnCancel)="DialogCtrl.close()" [nzConfirmLoading]="isSubmiting" nzOkText="保存" (nzOnOk)="savePassword()"></nz-modal>
这个nz-modal是一个弹窗,在组件里咱们只有一个变量的声明,如此简洁!而在html里DialogCtrl.isVisible,DialogCtrl.close()的形式也很容易理解它的做用和出处。this
这样作的另外一个好处是利于实现复用。对于能够复用的功能,好比上面发送验证码的逻辑,能够建一个全局的服务来提供。在angular里,经过angular的服务和依赖注入能够很轻松的实现,这里是我集中功能的common.service.ts服务文件:spa
common.servide.ts文件:code
@Injectable() export class CommonService { // 功能类集合 public DialogCtrl = DialogCtrl; public MessageCodeCtrl = MessageCodeCtrl; public CheckPasswordCtrl = CheckPasswordCtrl; constructor( private http: HttpClient ) { } /* 获取短信验证码(这些功能须要用到的方法) -------------------------- */ public getVerificationCode (phoneNum: string): Observable<any> { return this.http.get('/account/short_message?phoneNumber=' + phoneNum); } }
须要的地方只要注入这个服务就能够获取想要的功能。相比较直接创建一个组件来实现,我以为这样写有一些优点:orm
不知道大伙儿有没有这样的感受,本身写新项目的时候以为逻辑清晰,代码简练,功能也都实现了,可是过一段时间去看或者要改本身的代码的时候...哇,看不懂。
前端复杂的地方源于数不清的状态,因而我为那些有复杂状态的组件创建一个集中管理状态的对象(这里我取名为Impure):
/* 变量定义 -- 状态 -------------------------- */ registerForm: FormGroup; // 注册帐号表单 registerInfoForm: FormGroup; // 公司信息表单 isSubmitting = false; // 表单是否正在提交 nowForm = 'registerForm'; // 当前正在操做的表单 MessageCodeCtrl = new this.CommonService.MessageCodeCtrl(this.Msg, this.CommonService); // 验证码控制 /* 变量定义 -- 定值 -------------------------- */ registerFormSubmitAttr = ['login', 'password', 'shortMessageCode', 'roles', 'langKey']; registerInfoFormFormSubmitAttr = ['simName', 'contacter', 'officeTel', 'uid']; /* 改变状态事件 -------------------------- */ Impure = { // 表单初始化 RegisterFormInit: () => this.registerForm = this.registerFormInit(), RegisterInfoFormInit: () => this.registerInfoForm = this.registerInfoFormInit(), // 验证码不合法 MessageCodeInvalid: { notSend: () => this.Msg.error('您还未发送验证码'), notRight: () => this.Msg.error('验证码错误') }, // 表单提交 FormSubmit: { invalid: () => this.Msg.error('表单填写有误'), before: () => this.isSubmitting = true, registerOk: () => { this.Msg.success('帐号注册成功'); this.nowForm = 'registerInfoForm'; }, registerInfoOk: () => { this.Msg.success('保存信息成功!请耐心等待管理员审核'); this.Router.navigate(['/login']); }, fail: () => this.Msg.error('提交失败,请重试'), after: () => this.isSubmitting = false } };
这是一个简单的有两个表单的注册组件,由于两个表单html耦合度很高,因此写在了一块儿。
在组件内将变量分为状态和定值的两类,声明了一个Impure对象来集中管理这些状态,原则上这个组件里全部状态的改变都写在Impure里,而将事件触发的判断条件,数据处理写在Impure外面。
能够对比下这两个使用Impure和不使用Impure的表单提交方法:
/* 注册帐号表单提交(Impure) -------------------------- */ async register (form) { const _ = this.Fp._; // ramda库,用于数据处理 const { MessageCodeInvalid, FormSubmit } = this.Impure; // 表单不合法 if (form.invalid) { FormSubmit.invalid(); return; } // 验证码不合法 if (!this.MessageCodeCtrl.code) { MessageCodeInvalid.notSend(); return; } if (this.MessageCodeCtrl.code !== form.controls.shortMessageCode.value) { MessageCodeInvalid.notRight(); return; } // 表单提交 FormSubmit.before(); const data = _.compose(_.pick(this.registerFormSubmitAttr), _.map(_.prop('value')))(form.controls); // 数据处理 const res = await this.AccountService.producerRegisterFirst(data).toPromise(); if (!res) { FormSubmit.registerOk(); } else { FormSubmit.fail(); } FormSubmit.after(); } /* 公司信息表单提交(非Impure) -------------------------- */ async registerInfo ({ simName, contacter, officeTel }) { // 表单不合法 if (this.registerInfoForm.invalid) { this.Msg.error('表单填写有误'); return; } // 表单提交 this.isSubmitting = true; const data = { // 数据处理 simName: simName.value, contacter: contacter.value, officeTel: officeTel.value, uid: this.registerForm.controls.phone.value }; const res = await this.AccountService.producerRegisterSecond(data).toPromise(); if (!res) { this.Msg.success('保存信息成功!请耐心等待管理员审核'); this.Router.navigate(['/login']); } else { this.Msg.error('提交失败,请重试'); } this.isSubmitting = false; }
使用Impure管理状态后,逻辑清晰,在提交表单时你只须要关注事件发生的条件就能够了,而第二个条件和状态写在一块儿会很混乱,不能一眼清楚这个状态改变发生在何时,特别是你一段时间再来看的时候。
其实这里的数据处理(这里面的data)应该单独拿出来写一个方法的,我只是来顶一下用纯函数来处理数据的优势,这里的_是用了ramda这个库。相比较第二个处理方式,第一种方式更加优雅,简洁,很容易看出数据的源头是什么(这里是form.controls),单独抽离成数据处理函数也有很高的复用性。
假如某一天你要改下这里两个表格的成功后的状态,再也不须要到这两个长长的提交函数里找到它们而后一个一个改,只要在Impure里面改就能够了,你甚至不须要看那两个提交的方法。
这样子,一个组件能够大体分为状态,状态管理(impure),改变状态的事件(状态改变的判断条件),和数据处理(纯函数)四部分,各司其职,很好维护。
这些适合我但不必定适合全部人,每一个人都有本身的风格,各位看官感觉下就好。之后我有其它方面的总结也会拿出来分享。