利用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>
    )
}

 

posted @ 2020-05-03 19:58  Laeni  阅读(1130)  评论(0编辑  收藏  举报