最近開始維護項目,而後我發現天天的時間經常是花在修改幾個小功能上,改了問題有出了另外一個問題,思考哪裏不對?,而後這麼一天就過去了。。而後我就開始思考標題好讓個人時間不老是花在修改上,下面的是我最近的一些總結。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),改變狀態的事件(狀態改變的判斷條件),和數據處理(純函數)四部分,各司其職,很好維護。
這些適合我但不必定適合全部人,每一個人都有本身的風格,各位看官感覺下就好。之後我有其它方面的總結也會拿出來分享。