背景
教室内组件业务开发模式相似度较高,导致代码的重复度也较高,具体体现在
- 退订逻辑
退订逻辑包含在了TakeUntilDestory 中,无需手动管理
// beforeprivate subscription: Subscription;this.subscription = this.roomService.protos$.subscribe(proto => this.onProtoMessage(proto));this.subscription.add(xxx);this.subscription.add(xxx)ngOnDestroy() {this.subscription.unsubscribe();}// after@TakeUntilDestroyexport class TeamScoreRankComponent extends BaseClassRoomComponent implements OnDestroy {}
- 取值逻辑
使用 GetObservable 和 GetObservableValue 取 Observable 和取值
// beforethis.teamScoreRankState$ = this.store.select(getTeamScoreRankState).pipe(tap(state => {this.hidden = !state.showRank;}),);// after@GetObservable(getTeamScoreRankState)teamScoreRankState$;// beforeprivate teamId = 0;this.subscription.add(this.store.select(getTeam).subscribe(team => (this.teamId = team?.id ?? 0)// after@GetObservableValue(getTeam, team => team.id)readonly teamId = 0;
- 错误处理
使用 RunSafe 处理错误
// beforeprivate onRoomInfo(proto) {if (!proto) {return;}try{this.onTeamScoreState(proto.teamScoreState);this.tryGetTeamScoreRankWithApi();}catch(e){log.error('===')}}// after@RunSafe()private onRoomInfo(proto) {this.onTeamScoreState(proto.teamScoreState);this.tryGetTeamScoreRankWithApi();}
实现细节
- GetObservable ```typescript import { takeUntil } from ‘rxjs/operators’;
export function GetObservable
- GetObservableValue```typescriptimport { filter, map, takeUntil, tap } from 'rxjs/operators';export function GetObservableValue<T>(selector: any, mapFn: any = val => val, debug = false): Function {return (target: Record<any, any>, propName: string) => {let _val: T = target[propName];let ob = null;Object.defineProperty(target, propName, {get(): T | unknown {if (ob) {return _val;}ob = this.store.select(selector).pipe(filter(val => val !== null && val !== undefined),map(mapFn),tap(val => {// eslint-disable-next-line no-consoledebug && console.log(`=== GetObservableValue ${selector.name} ===`, val);}),takeUntil(this.takeUntilDestroy$),).subscribe(val => (_val = val));this.takeUntilDestroy$.subscribe(_ => (ob = null));return _val;},set(value) {_val = value;},});};}
- TakeUntilDestroy
``typescript export function TakeUntilDestroy(constructor: Function): void { const originalDestroy = constructor.prototype.ngOnDestroy; if (typeof originalDestroy !== 'function') { // eslint-disable-next-line no-console console.warn(${constructor.name} is using @TakeUntilDestroy but does not implement OnDestroy`); } constructor.prototype.ngOnDestroy = function destroy(…args): void { if (typeof originalDestroy === ‘function’) {
} if (this.takeUntilDestroy$) {originalDestroy.apply(this, args);
} else {this.takeUntilDestroy$.next();this.takeUntilDestroy$.complete();
} }; }// eslint-disable-next-line no-consoleconsole.error(`[Decorator TakeUntilDestroy] this.takeUntilDestroy$ does not exist which may cause memory leak, make sure your component extends InRoomBaseComponent or OutRoomBaseComponent`,);
- RunSafe```typescriptinterface IParams {returnValue: any;}type SafeDecorator<T, K extends keyof T> = T[K] | K;export function RunSafe(params: IParams = { returnValue: undefined }): Function {return function safe(target: Record<string, any>,propertyKey: string,descriptor: TypedPropertyDescriptor<Function>,): TypedPropertyDescriptor<Function> {const originalMethod = descriptor.value;descriptor.value = function SafeWrapper() {try {return originalMethod.apply(this, arguments);} catch (error) {// eslint-disable-next-line no-consoleconsole.error(`[Safe Decorator]: execute ${propertyKey} function error:`, error);return params.returnValue !== undefined ? params.returnValue : false;}};return descriptor;};}
