功能分離
這個算是面向?qū)ο罄锏乃枷敕课纾诮M件里,有很多功能是獨立的,比如最常見的發(fā)送驗證碼郭厌,確認密碼等。把這些邏輯封裝成一個或幾個函數(shù)寫在組件里的話雕蔽,這在組件很小的時候沒有什么影響折柠,但是當組件功能比較復雜的時候,就會有些問題:
- 組件邏輯區(qū)域會變的很大批狐,各種方法混雜很難一眼辨識
- 因為定義功能需要的變量和方法不在一起扇售,導致修改麻煩
功能分離就是把這些功能抽離出來,寫出一個類嚣艇,然后在組件里引入承冰。
下面是一個簡單的彈窗控制的功能的類和這個類的使用:
export class DialogCtrl {
isVisible = false;
open () {
this.isVisible = true;
}
close () {
this.isVisible = false;
}
}
然后在需要的組件里引入并實例化:
DialogCtrl = new this.CommonService.DialogCtrl(); // 是否打開彈窗
在html里可以直接這樣用:
<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()的形式也很容易理解它的作用和出處贰谣。
這樣做的另一個好處是利于實現(xiàn)復用娜搂。對于可以復用的功能,比如上面發(fā)送驗證碼的邏輯吱抚,可以建一個全局的服務來提供百宇。在angular里,通過angular的服務和依賴注入可以很輕松的實現(xiàn)秘豹,這里是我集中功能的common.service.ts服務文件:
common.servide.ts文件:
@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);
}
}
需要的地方只要注入這個服務就可以獲取想要的功能携御。相比較直接建立一個組件來實現(xiàn),我覺得這樣寫有一些優(yōu)勢:
靈活性更高既绕。寫成組件會有樣式的限制啄刹,而這樣寫沒有。
更簡潔岸更。寫成組件鸵膏,與之溝通只能通過子父組件的傳入變量,監(jiān)聽子組件事件的方法怎炊,你使用的組件不可避免的會多出這些變量和方法谭企。
狀態(tài)管理
不知道大伙兒有沒有這樣的感覺,自己寫新項目的時候覺得邏輯清晰评肆,代碼簡練债查,功能也都實現(xiàn)了,但是過一段時間去看或者要改自己的代碼的時候...哇瓜挽,這是什么玩意兒盹廷。至少我有過:flushed:
前端復雜的地方源于數(shù)不清的狀態(tài),于是我為那些有復雜狀態(tài)的組件建立一個集中管理狀態(tài)的對象(這里我取名為Impure):
/* 變量定義 -- 狀態(tài)
-------------------------- */
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'];
/* 改變狀態(tài)事件
-------------------------- */
Impure = {
// 表單初始化
RegisterFormInit: () => this.registerForm = this.registerFormInit(),
RegisterInfoFormInit: () => this.registerInfoForm = this.registerInfoFormInit(),
// 驗證碼不合法
MessageCodeInvalid: {
notSend: () => this.Msg.error('您還未發(fā)送驗證碼'),
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耦合度很高管怠,所以寫在了一起。
在組件內(nèi)將變量分為狀態(tài)和定值的兩類缸榄,聲明了一個Impure對象來集中管理這些狀態(tài)渤弛,原則上這個組件里所有狀態(tài)的改變都寫在Impure里,而將事件觸發(fā)的判斷條件甚带,數(shù)據(jù)處理寫在Impure外面她肯。
可以對比下這兩個使用Impure和不使用Impure的表單提交方法:
/* 注冊賬號表單提交(Impure)
-------------------------- */
async register (form) {
const _ = this.Fp._; // ramda庫,用于數(shù)據(jù)處理
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); // 數(shù)據(jù)處理
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 = { // 數(shù)據(jù)處理
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管理狀態(tài)后鹰贵,邏輯清晰晴氨,在提交表單時你 只需要關(guān)注事件發(fā)生的條件 就可以了,而第二個條件和狀態(tài)寫在一起會很混亂(其實就是我以前寫的)碉输,不能一眼清楚這個狀態(tài)改變發(fā)生在什么時候籽前,特別是你一段時間再來看的時候。
其實這里的數(shù)據(jù)處理(這里面的data)應該單獨拿出來寫一個方法的腊瑟,我只是來頂一下用純函數(shù)來處理數(shù)據(jù)的優(yōu)點聚假,這里的_是用了ramda這個庫。相比較第二個處理方式闰非,第一種方式更加優(yōu)雅膘格,簡潔,很容易看出數(shù)據(jù)的源頭是什么(這里是form.controls),單獨抽離成數(shù)據(jù)處理函數(shù)也有很高的復用性财松。
假如某一天你要改下這里兩個表格的成功后的狀態(tài)瘪贱,不再需要到這兩個長長的提交函數(shù)里找到它們?nèi)缓笠粋€一個改,只要在Impure里面改就可以了辆毡,你甚至不需要看那兩個提交的方法菜秦。
這樣子,一個組件可以大致分為狀態(tài)舶掖,狀態(tài)管理(impure)球昨,改變狀態(tài)的事件(狀態(tài)改變的判斷條件),和數(shù)據(jù)處理(純函數(shù))四部分眨攘,各司其職主慰,很好維護。
結(jié)語
這些適合我但不一定適合所有人鲫售,每個人都有自己的風格共螺,各位看官感受下就好。以后我有其它方面的總結(jié)也會拿出來分享情竹。
學習前端的同學注意了C瓴弧!!
學習過程中遇到什么問題或者想獲取學習資源的話雏蛮,歡迎加入前端學習交流群461593224涎嚼,我們一起學前端!