Angular實(shí)踐之將Input與Lifecycle轉(zhuǎn)換成流示例詳解
將 Input 和生命周期函數(shù)轉(zhuǎn)換成流
在 Angular 中一直有一個(gè)期待,就是希望能夠?qū)?Input 和生命周期函數(shù)轉(zhuǎn)換成流,實(shí)際上我們可以通過比較簡(jiǎn)單的方式實(shí)現(xiàn),比如說:
class NameComponent {
@Input() name: string;
}
我們要實(shí)現(xiàn)一個(gè) input 為 name, output 為 hello name 的簡(jiǎn)單 component。如果將 input 看成是一個(gè)流的話,就會(huì)比較容易實(shí)現(xiàn)。常見的流轉(zhuǎn)換方式是通過 set 方法實(shí)現(xiàn):
class NameComponent {
private name$ = new Subject(1);
private _name: string;
@Input() set name(val: string) {
this.name$.next(val);
this._name = val;
}
get name() {
return this._name;
}
@Output() helloName = this.name$.pipe(
map(name => `hello ${name}`),
);
}
這樣寫是可以,不過你也看出來了,有一個(gè)問題,就是麻煩。
對(duì)于生命周期函數(shù),我們也有類似的需求。比如說,我們經(jīng)常需要在 Angular 銷毀的時(shí)候,unsubscribe 所有的 subscription。
class NameComponent implements OnDestroy {
private destory$ = new Subject<void>();
ngOnDestroy(): void {
destory$.next();
destory$.complete();
}
}
如果需要使用其他的生命周期函數(shù)的話,每個(gè)函數(shù)都需要這樣手動(dòng)調(diào)用一次。
思路
如果回到 input 的問題的話,我們知道,獲取 input 的變化,除了 set 方法,還有 ngOnChanges 函數(shù)。我們很容易想到一個(gè)思路:
- 將 ngOnChanges 轉(zhuǎn)換成一個(gè) stream, onChanges$
- 通過 onChanges$ map 成 input stream
private onChanges$ = new Subject<SimpleChanges>();
@Input() name: string;
name$ = this.onChanges$.pipe(
switchMap(simpleChanges => {
if ('name' in simpleChanges) {
return of(simpleChanges?.name?.currentValue);
}
return EMPTY;
}),
)
ngOnChanges(simpleChanges: SimpleChanges) {
this.onChanges$.next(simpleChanges);
}
當(dāng)然,ngOnChanges 只會(huì)在 input 變化的時(shí)候觸發(fā),所以我們還需要加上 init 以后的初始值。(當(dāng)然,我們也要將 afterViewInit 轉(zhuǎn)換成 stream)
name$ = afterViewInit$.pipe(
take(1),
map(() => this.name),
switchMap(value => this.onChanges$.pipe(
startWith(value),
if ('name' in simpleChanges) {
return of(simpleChanges?.name?.currentValue);
}
return EMPTY;
)),
)
抽離成一個(gè)方法
很明顯,如果 input 比較多的話,這樣寫就比較冗余了。很容易想到我們可以把它抽離成一個(gè)方法。
export function getMappedPropsChangesWithLifeCycle<T, P extends (keyof T & string)>(
target: T,
propName: P,
onChanges$: Observable<SimpleChanges>,
afterViewInit$: Observable<void>) {
if (!onChanges$) {
return EMPTY;
}
if (!afterViewInit$) {
return EMPTY;
}
return afterViewInit$.pipe(
take(1),
map(() => target?.[propName]),
switchMap(value => target.onChanges$.pipe(
startWith(value),
if (propName in simpleChanges) {
return of(simpleChanges?.[propName]?.currentValue);
}
return EMPTY;
))
)
}
看到這里,你可能很容易就想到了,我們可以把這個(gè)方法變成一個(gè) decorator,這樣看起來就簡(jiǎn)潔多了。比如我們定義一個(gè)叫做 InputMapper 的裝飾器:
export function InputMapper(inputName: string) {
return function (target: object, propertyKey: string) {
const instancePropertyKey = Symbol(propertyKey);
Object.defineProperty(target, propertyKey, {
get: function () {
if (!this[instancePropertyKey]) {
this[instancePropertyKey] = getMappedPropsChangesWithLifeCycle(this, inputName, this['onChanges$']!, this['afterViewInit$']!);
}
return this[instancePropertyKey];
}
});
};
}
值得注意的是,因?yàn)?target 會(huì)是 component instance 的 proto,會(huì)被所有的 instance 共享,所以我們?cè)诙x變量的時(shí)候,可以通過 defineProperty 中的 get 函數(shù)將變量定義到 this 上。這樣 component instance 在調(diào)用的時(shí)候就可以成功將內(nèi)容 apply 到 instance 而費(fèi) component class 的 prototype 上。
當(dāng)然,使用的時(shí)候就會(huì)方便很多了:
class NameComponent {
private onChanges$ = new Subject<SimpleChanges>();
private afterViewInit$ = new Subject<void>();
@Input() name: string;
@InputMapper('name') name$!: Observable<string>;
ngOnChanges() {
...
}
ngAfterViewInit() {
...
}
}
當(dāng)然,因?yàn)閷?duì)于生命周期函數(shù),也是重復(fù)性工作,我們很容易想到,是否能夠也能通過裝飾器實(shí)現(xiàn)。
重寫生命周期函數(shù)
我們只需要重寫生命周期函數(shù)就可以巧妙的實(shí)現(xiàn)了:
- 用類似的方法,在 this 定義 subject
- 保存原有的函數(shù)
- 通過 prototype 重寫原來的函數(shù),調(diào)用 subject.next。
- 可以參考這里的實(shí)現(xiàn): Angular2+ Observable Life-cycle Events (induro.io)
export function LifeCycleStream(lifeCycleMethodName: LifeCycleMethodName) {
return (target: object, propertyKey: string) => {
const originalLifeCycleMethod = target.constructor.prototype[lifeCycleMethodName];
const instanceSubjectKey = Symbol(propertyKey);
Object.defineProperty(target, propertyKey, {
get: function () {
if (!this[instanceSubjectKey]) {
this[instanceSubjectKey] = new ReplaySubject(1);
}
return this[instanceSubjectKey].asObservable();
}
});
target.constructor.prototype[lifeCycleMethodName] = function () {
if (this[instanceSubjectKey]) {
this[instanceSubjectKey].next.call(this[instanceSubjectKey], arguments[0]);
}
if (originalLifeCycleMethod && typeof originalLifeCycleMethod === 'function') {
originalLifeCycleMethod.apply(this, arguments);
}
};
}
}
那么我們可以將之前工作簡(jiǎn)化為:
class NameComponent {
@LifeCycleStream('ngOnChanges') onChanges$: Observable<SimpleChanges>;
@LifeCycleStream('ngAfterViewInit') ngAfterViewInit$: Observable<void>;
@Input() name: string;
@InputMapper('name') name$!: Observable<string>;
...
...
}
然后,因?yàn)槲覀円呀?jīng)實(shí)現(xiàn)了 InputMapper,那么很容易想到,有沒有可能把 onChanges$ 和 afterViewInit$ 放進(jìn) InputMapper,這樣我們就可以減少重復(fù)的調(diào)用了。我們可以把 LifeCycleStream 中的主體邏輯抽離成一個(gè)方法:applyLifeCycleObservable,然后在 InputMapper 調(diào)用就可以了:
if (!('afterViewInit$' in target)) {
applyLifeCycleObservable('ngAfterViewInit', target, 'afterViewInit$');
}
if (!('onChanges$' in target)) {
applyLifeCycleObservable('ngOnChanges', target, 'onChanges$');
}
當(dāng)然,我們?cè)谡{(diào)用前需要檢查這個(gè) stream 是否已經(jīng)存在。注意,這里不要直接調(diào)用 target['ngAfterViewInit'], 因?yàn)槲覀冎皩懥?get 函數(shù)??梢运伎枷聻槭裁础#ǚ乐箤?ngAfterViewInit apply 到 target 上去)
最后,我們來看一下最終的代碼:
class NameComponent {
@Input() name: string;
@InputMapper('name') name$!: Observable<string>;
}
這樣,既沒有破壞已有的 angular input,又能夠很快的實(shí)現(xiàn),input to stream 的轉(zhuǎn)換,還是比較方便的。
以上就是Angular實(shí)踐之將Input與Lifecycle轉(zhuǎn)換成流示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Angular將Input Lifecycle轉(zhuǎn)流的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
AngularJS框架中的雙向數(shù)據(jù)綁定機(jī)制詳解【減少需要重復(fù)的開發(fā)代碼量】
這篇文章主要介紹了AngularJS框架中的雙向數(shù)據(jù)綁定機(jī)制,結(jié)合實(shí)例形式分析了AngularJS雙向數(shù)據(jù)綁定機(jī)制的原理及實(shí)現(xiàn)方法,以及減少需要重復(fù)開發(fā)代碼量的優(yōu)勢(shì),需要的朋友可以參考下2017-01-01
解決angular的$http.post()提交數(shù)據(jù)時(shí)后臺(tái)接收不到參數(shù)值問題的方法
這篇文章為大家分享了解決angular的$http.post()提交數(shù)據(jù)時(shí)后臺(tái)接收不到參數(shù)值問題的方法,感興趣的小伙伴們可以參考一下2015-12-12
AngularJS+bootstrap實(shí)現(xiàn)動(dòng)態(tài)選擇商品功能示例
這篇文章主要介紹了AngularJS+bootstrap實(shí)現(xiàn)動(dòng)態(tài)選擇商品功能,涉及AngularJS指令、事件響應(yīng)及頁(yè)面元素動(dòng)態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-05-05
AngularJS中控制器函數(shù)的定義與使用方法示例
這篇文章主要介紹了AngularJS中控制器函數(shù)的定義與使用方法,結(jié)合具體實(shí)例形式分析了AngularJS控制器函數(shù)的定義、綁定及相關(guān)使用技巧,需要的朋友可以參考下2017-10-10
Angular中ng-template和ng-container的應(yīng)用小結(jié)
Angular的日常工作中經(jīng)常會(huì)使用到ng-template和ng-container,本文對(duì)他們做一個(gè)總結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-06-06

