k8s angular mysql_Angular 实践:如何优雅地发起和处理请求
Tips: 本文實現(xiàn)重度依賴 ObservableInput,靈感來自靈雀云同事實現(xiàn)的 asyncData 指令,但之前沒有 ObservableInput 的裝飾器,處理響應(yīng) Input 變更相對麻煩一些,所以這里使用 ObservableInput 重新實現(xiàn)。
What And Why
大部分情況下處理請求有如下幾個過程:
看著很復(fù)雜的樣子,既要 Loading,又要 Reload,還要 Retry,如果用命令式寫法可能會很蛋疼,要處理各種分支,而今天要講的 rxAsync 指令就是用來優(yōu)雅地解決這個問題的。
How
我們來思考下如果解決這個問題,至少有如下四個點需要考慮。
1.發(fā)起請求有如下三種情況:
第一次渲染主動加載
用戶點擊重新加載
加載出錯自動重試
2.渲染的過程中需要根據(jù)請求的三種狀態(tài) ——?loading,?success,?error?(類似?Promise?的?pending,?resolved,?rejected) —— 動態(tài)渲染不同的內(nèi)容
3.輸入的參數(shù)發(fā)生變化時我們需要根據(jù)最新參數(shù)重新發(fā)起請求,但是當(dāng)用戶輸入的重試次數(shù)變化時應(yīng)該忽略,因為重試次數(shù)只影響?Error?狀態(tài)
4.用戶點擊重新加載可能在我們的指令內(nèi)部,也可能在指令外部
Show Me the Code
話不多說,上代碼:
@Directive({
selector: '[rxAsync]',
})
export class AsyncDirective
implements OnInit, OnDestroy {
@ObservableInput()
@Input('rxAsyncContext')
private context$!: Observable // 自定義 fetcher 調(diào)用時的 this 上下文,還可以通過箭頭函數(shù)、fetcher.bind(this) 等方式解決
@ObservableInput()
@Input('rxAsyncFetcher')
private fetcher$!: Observable>> // 自動發(fā)起請求的回調(diào)函數(shù),參數(shù)是下面的 params,應(yīng)該返回 Observable
@ObservableInput()
@Input('rxAsyncParams')
private params$!: Observable
// fetcher 調(diào)用時傳入的參數(shù)
@Input('rxAsyncRefetch')
private refetch$$ = new Subject() // 支持用戶在指令外部重新發(fā)起請求,用戶可能不需要,所以設(shè)置一個默認(rèn)值
@ObservableInput()
@Input('rxAsyncRetryTimes')
private retryTimes$!: Observable // 發(fā)送 Error 時自動重試的次數(shù),默認(rèn)不重試
private destroy$$ = new Subject()
private reload$$ = new Subject()
private context = {
reload: this.reload.bind(this), // 將 reload 綁定到 template 上下文中,方便用戶在指令內(nèi)重新發(fā)起請求
} as IAsyncDirectiveContext
private viewRef: Nullable
private sub: Nullable
constructor(
private templateRef: TemplateRef,
private viewContainerRef: ViewContainerRef,
) {}
reload() {
this.reload$$.next()
}
ngOnInit() {
// 得益于 ObservableInput ,我們可以一次性響應(yīng)所有參數(shù)的變化
combineLatest([
this.context$,
this.fetcher$,
this.params$,
this.refetch$$.pipe(startWith(null)), // 需要 startWith(null) 觸發(fā)第一次請求
this.reload$$.pipe(startWith(null)), // 同上
])
.pipe(
takeUntil(this.destroy$$),
withLatestFrom(this.retryTimes$), // 忽略 retryTimes 的變更,我們只需要取得它的最新值即可
)
.subscribe(([[context, fetcher, params], retryTimes]) => {
// 如果參數(shù)變化且上次請求還沒有完成時,自動取消請求忽略掉
this.disposeSub()
// 每次發(fā)起請求前都重置 loading 和 error 的狀態(tài)
Object.assign(this.context, {
loading: true,
error: null,
})
this.sub = fetcher
.call(context, params)
.pipe(
retry(retryTimes), // 錯誤時重試
finalize(() => {
// 無論是成功還是失敗,都取消 loading,并重新觸發(fā)渲染
this.context.loading = false
if (this.viewRef) {
this.viewRef.detectChanges()
}
}),
)
.subscribe(
data => (this.context.$implicit = data),
error => (this.context.error = error),
)
if (this.viewRef) {
return this.viewRef.markForCheck()
}
this.viewRef = this.viewContainerRef.createEmbeddedView(
this.templateRef,
this.context,
)
})
}
ngOnDestroy() {
this.disposeSub()
this.destroy$$.next()
this.destroy$$.complete()
if (this.viewRef) {
this.viewRef.destroy()
this.viewRef = null
}
}
disposeSub() {
if (this.sub) {
this.sub.unsubscribe()
this.sub = null
}
}
}
Usage
總共 100 多行的源碼,說是很優(yōu)雅,那到底使用的時候優(yōu)不優(yōu)雅呢?來個實例看看:
@Component({
selector: 'rx-async-directive-demo',
template: `
Refetch (Outside rxAsync)
*rxAsync="
let todo;
let loading = loading;
let error = error;
let reload = reload;
context: context;
fetcher: fetchTodo;
params: todoId;
refetch: refetch$$;
retryTimes: retryTimes
"
>
Reload
loading: {{ loading }} error: {{ error | json }}
todo: {{ todo | json }}
`,
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class AsyncDirectiveComponent {
context = this
@Input()
todoId = 1
@Input()
retryTimes = 0
refetch$$ = new Subject()
constructor(private http: HttpClient) {}
fetchTodo(todoId: string) {
return typeof todoId === 'number'
? this.http.get('//jsonplaceholder.typicode.com/todos/' + todoId)
: EMPTY
}
}
總結(jié)
以上是生活随笔為你收集整理的k8s angular mysql_Angular 实践:如何优雅地发起和处理请求的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美元降息对a股影响
- 下一篇: xampp mysql登录失败_XAMP