利用rxjs库的Subject多播解决在第一次订阅时进行初始化操作(如第一次订阅时从服务器获取数据)
rxjs 库中 Subject 类可以用于实现多播,其本身已经有多个实现(可参考【Rxjs】 - 解析四种主题Subject),但是都是需要手动操作Subject对象进行发布。
这里通过继承 BehaviorSubject(Subject的实现类,也可以直接继承Subject,但这里考虑到可能会用到初始值的时候)实现在第一次订阅(调用subscribe方法)时进行初始化(可以从服务器获取数据)。
第一步: InitSubject 类定义
import { ReplaySubject, Subscription, TeardownLogic } from 'rxjs'; /** * 第一次订阅时执行初始化操作. * <p> * 和ReplaySubject一样,都会记录下最新的一个历史数据,以便有新订阅者时直接使用历史值。 * 区别在于: * ReplaySubject 有初始值,第一次订阅会得到初始值, * 而InitSubject第一次订阅则什么也不返回,但是第一次订阅会触发数据加载,加载成功后才对所有订阅者进行推送。 */ export class InitSubject<T> extends ReplaySubject<T> { /** * 是否为第一次 */ private first = true; constructor(private init?: (subject: InitSubject<T>) => TeardownLogic) { super(1); } // @ts-ignore 这里ts无法检查通过的原因是父类中存在一个弃用的重载函数所致 subscribe(next: (value: T) => void): Subscription { if (this.first && this.init) { this.first = false; this.init(this); } return super.subscribe(next); } } /** * 一个便捷操作。创建InitSubject的同时也会返回一个重新加载函数(reload),以便在合适的时间重新加载数据。 * * @param load 加载数据函数(InitSubject 并不知道如何加载数据,所以需要使用者提供) */ export function create<T>(load: (observer: (t: T) => void) => void): [InitSubject<T>, () => Promise<void>] { /** 可观察对象 */ const subject$ = new InitSubject<T>(subject => load(v => subject.next(v))); // 重新加载函数 const reload = async (): Promise<void> => { return new Promise((resolve) => load(v => { subject$.next(v); resolve(); })); }; return [subject$, reload]; }
第二步: 使用
import { create } from "@/services/InitSubject"; import request from "@/services/request"; /** 通过上面创建的 create 方法得到一个可观察对象(用于监听数据变化)和一个 reload 函数(用于需要重新加载数据时调用) */ export const [allAppSubject$, reloadAllApp] = create((observer: (item: App[]) => void) => { request.get<App[]>('/api/v1/app/list').then(observer); }); // React 中使用示例 function Test() { const [app, setApps] = useState<App[]>([]); useEffect(() => { const subscription = allAppSubject$.subscribe(setApps); // 注意一定要取消订阅,否则可能导致内存泄露或者出现意想不到的结果 return () => subscription.unsubscribe(); }, []); console.log(app); return ( <button onClick={() => reloadAllApp()}>重新加载</button> ) }