处理 Loading/Error 状态
处理 Effect 中类似 AJAX 请求的 loading
和 error
状态是非常常见的需求。在这里我们推荐以下方式实现这个需求:
在 AppState 的一个值中存储所有可能的状态
通常来说,将所有状态拆分存储在 State 里是比较常见的处理方式,但是也是最繁琐的方式。更优雅的方法是将这些可能的状态都存储在一个值里面,TypeScript
的类型系统会保证你在各种状态下都能正确的渲染对应的组件:
import { Module, EffectModule, Reducer, Effect, Action } from "@sigi/core";import { Observable, of } from "rxjs";import {exhaustMap,takeUntil,map,tap,startWith,catchError,} from "rxjs/operators";import { HttpClient } from "./http.service";interface AppState {list: string[] | null | Error;}@Module("App")export class AppModule extends EffectModule<AppState> {defaultState: AppState = {list: [],};constructor(private readonly httpClient: HttpClient) {super();}@Reducer()cancel(state: AppState) {return { ...state, ...this.defaultState };}@Reducer()setList(state: AppState, list: AppState["list"]) {return { ...state, list };}@Effect()fetchList(payload$: Observable<void>): Observable<Action> {return payload$.pipe(exhaustMap(() => {return this.httpClient.get(`/resources`).pipe(tap(() => {console.info("Got response");}),map((response) => this.getActions().setList(response)),catchError((e) => of(this.getActions().setList(e))),startWith(this.getActions().setList(null)),takeUntil(this.getAction$().cancel));}));}}
import { Injectable } from "@sigi/di";import { Observable, timer, throwError } from "rxjs";import { map } from "rxjs/operators";@Injectable()export class HttpClient {get(_url: string): Observable<string[]> {return Math.random() > 0.5? throwError(new TypeError("Fail to fetch")): timer(3000).pipe(map(() => ["mock1", "mock2", "mock3"]));}}
import "reflect-metadata";import React from "react";import { render } from "react-dom";import { useModule } from "@sigi/react";import { initDevtool } from "@sigi/devtool";import { AppModule } from "./app.module";function App() {const [{ list }, dispatcher] = useModule(AppModule);const loading = !list ? <div>loading</div> : null;const title =list instanceof Error ? (<h1>{list.message}</h1>) : (<h1>Hello CodeSandbox</h1>);const listNodes = Array.isArray(list)? list.map((value) => <li key={value}>{value}</li>): null;return (<div>{title}<button onClick={dispatcher.fetchList}>fetchList</button><button onClick={dispatcher.cancel}>cancel</button>{loading}<ul>{listNodes}</ul></div>);}const rootElement = document.getElementById("app");render(<App />, rootElement);initDevtool();