We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[zh]
Tips: 本文实现重度依赖 ObservableInput,灵感来自同事 @Mengqi Zhang 实现的 asyncData 指令,但之前没有 ObservableInput 的装饰器,处理响应 Input 变更相对麻烦一些,所以这里使用 ObservableInput 重新实现。
asyncData
ObservableInput
Input
大部分情况下处理请求有如下几个过程:
看着很复杂的样子,既要 Loading,又要 Reload,还要 Retry,如果用命令式写法可能会很蛋疼,要处理各种分支,而今天要讲的 rxAsync 指令就是用来优雅地解决这个问题的。
Loading
Reload
Retry
rxAsync
我们来思考下如果解决这个问题,至少有如下四个点需要考虑。
loading
success
error
Promise
pending
resolved
rejected
Error
话不多说,上代码:
@Directive({ selector: '[rxAsync]', }) export class AsyncDirective<T, P, E = HttpErrorResponse> implements OnInit, OnDestroy { @ObservableInput() @Input('rxAsyncContext') private context$!: Observable<any> // 自定义 fetcher 调用时的 this 上下文,还可以通过箭头函数、fetcher.bind(this) 等方式解决 @ObservableInput() @Input('rxAsyncFetcher') private fetcher$!: Observable<Callback<[P], Observable<T>>> // 自动发起请求的回调函数,参数是下面的 params,应该返回 Observable @ObservableInput() @Input('rxAsyncParams') private params$!: Observable<P> // fetcher 调用时传入的参数 @Input('rxAsyncRefetch') private refetch$$ = new Subject<void>() // 支持用户在指令外部重新发起请求,用户可能不需要,所以设置一个默认值 @ObservableInput() @Input('rxAsyncRetryTimes') private retryTimes$!: Observable<number> // 发送 Error 时自动重试的次数,默认不重试 private destroy$$ = new Subject<void>() private reload$$ = new Subject<void>() private context = { reload: this.reload.bind(this), // 将 reload 绑定到 template 上下文中,方便用户在指令内重新发起请求 } as IAsyncDirectiveContext<T, E> private viewRef: Nullable<ViewRef> private sub: Nullable<Subscription> constructor( private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef, ) {} reload() { this.reload$$.next() } ngOnInit() { // 得益于 ObservableInput ,我们可以一次性响应所有参数的变化 combineLatest([ this.context$, this.fetcher$, this.params$, this.refetch$$.pipe(startWith(null)), // 需要 startWith(null) 触发第一次请求 this.reload$$.pipe(startWith(null)), // 同上 ]) .pipe( takeUntil(this.destroy$$), withLatestFrom(this.retryTimes$), // 忽略 retryTimes 的变更,我们只需要取得它的最新值即可 ) .subscribe(([[context, fetcher, params], retryTimes]) => { // 每次发起请求前都重置 loading 和 error 的状态 Object.assign(this.context, { loading: true, error: null, }) // 如果参数变化且上次请求还没有完成时,自动取消请求忽略掉 this.disposeSub() this.sub = fetcher .call(context, params) .pipe( retry(retryTimes), // 错误时重试 finalize(() => { this.context.loading = false // 无论是成功还是失败,都取消 loading,并重新触发渲染 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 } } }
总共 100 多行的源码,说是很优雅,那到底使用的时候优不优雅呢?来个实例看看:
@Component({ selector: 'rx-async-directive-demo', template: ` <button (click)="refetch$$.next()">Refetch (Outside rxAsync)</button> <div *rxAsync=" let todo; let loading = loading; let error = error; let reload = reload; context: context; fetcher: fetchTodo; params: todoId; refetch: refetch$$; retryTimes: retryTimes " > <button (click)="reload()">Reload</button> loading: {{ loading }} error: {{ error | json }} <br /> todo: {{ todo | json }} </div> `, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, }) class AsyncDirectiveComponent { context = this @Input() todoId = 1 @Input() retryTimes = 0 refetch$$ = new Subject<void>() constructor(private http: HttpClient) {} fetchTodo(todoId: string) { return typeof todoId === 'number' ? this.http.get('//jsonplaceholder.typicode.com/todos/' + todoId) : EMPTY } }
Online Demo
各位看官觉得如何呢?
在不使用 rxjs 的 React, Vue 中我们是如何解决这些问题的?
rxjs
React
Vue
ObservableInput 与本文的 rxAsync 已发布到 @rxts/ngrx ,欢迎试用。
源码仓库:GitHub,欢迎 PR 或提交 issue 。
PR
issue
本文流程图使用 mermaid 制作。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
[zh]
Tips: 本文实现重度依赖 ObservableInput,灵感来自同事 @Mengqi Zhang 实现的
asyncData
指令,但之前没有ObservableInput
的装饰器,处理响应Input
变更相对麻烦一些,所以这里使用ObservableInput
重新实现。What And Why
大部分情况下处理请求有如下几个过程:
看着很复杂的样子,既要
Loading
,又要Reload
,还要Retry
,如果用命令式写法可能会很蛋疼,要处理各种分支,而今天要讲的rxAsync
指令就是用来优雅地解决这个问题的。How
我们来思考下如果解决这个问题,至少有如下四个点需要考虑。
loading
,success
,error
(类似Promise
的pending
,resolved
,rejected
) —— 动态渲染不同的内容Error
状态Show Me the Code
话不多说,上代码:
Usage
总共 100 多行的源码,说是很优雅,那到底使用的时候优不优雅呢?来个实例看看:
Online Demo
各位看官觉得如何呢?
Think More
在不使用
rxjs
的React
,Vue
中我们是如何解决这些问题的?Advertisement
ObservableInput
与本文的rxAsync
已发布到 @rxts/ngrx ,欢迎试用。源码仓库:GitHub,欢迎
PR
或提交issue
。本文流程图使用 mermaid 制作。
The text was updated successfully, but these errors were encountered: