鴻蒙極速入門(六)-載入請求狀態管理-LoadState+觀察者模式

2023-10-31 18:01:58

背景

1、在ArkTS的架構中,沒有明確的可管理的載入請求狀態的腳手架,在進行網路請求過程中,無法簡單的進行互動響應。

2、參考Android中的LoadState寫了一個簡單的腳手架,以便在日常開發過程中,管理載入請求狀態和UI互動。

腳手架說明與原始碼

1、狀態機LoadState

使用一個狀態機,分別對應網路請求過程中的Loading(發起請求)、Loaded(請求成功)、LoadError(請求失敗)狀態,並支援鏈式呼叫:

/**
 * 網路請求MVVM資料模型,由子類實現狀態機管理,由方法實現回撥監聽
 */
export abstract class LoadState {
  /**
   * loading函數,如果當前狀態是Loading,則呼叫回撥函數
   * @param callBack 回撥函數
   * @returns this
   */
  loading(callBack?: () => void): this {
    if (this instanceof Loading) {
      callBack?.call(null);
    }
    return this;
  }

  /**
   * loaded函數,如果當前狀態是Loaded,則呼叫回撥函數
   * @param callBack 回撥函數
   * @returns this
   */
  loaded(callBack?: (result: Loaded<any>) => void): this {
    if (this instanceof Loaded) {
      callBack?.call(null, this);
    }
    return this;
  }

  /**
   * loadError函數,如果當前狀態是LoadError,則呼叫回撥函數
   * @param callBack 回撥函數
   * @returns this
   */
  loadError(callBack?: (error: LoadError) => void): this {
    if (this instanceof LoadError) {
      callBack?.call(null, this);
    }
    return this;
  }
}

/**
 * Loading類,繼承自LoadState類
 */
export class Loading extends LoadState {}

/**
 * Loaded類,繼承自LoadState類,包含一個result屬性和一個data方法
 */
export class Loaded<T> extends LoadState {
  result?: T;

  constructor(data: T) {
    super();
    this.result = data;
  }

  data(): T | undefined {
    return this.result;
  }
}

/**
 * LoadError類,繼承自LoadState類,包含code和message屬性
 */
export class LoadError extends LoadState {
  code?: number;
  message?: string;

  constructor(code: number, message: string) {
    super();
    this.code = code;
    this.message = message;
  }
}

2、觀察者模式

ArtTS沒有提供開箱即用的觀察者模式框架,也無法直接使用RxJS框架,所以自己手寫一個簡單的ValueNotifier作為觀察者實現類:

/**
 * ValueNotifier類,包含_value、listeners屬性和addListener、notifyListeners、value方法
 */
export class ValueNotifier<T> {
  private _value: T;
  listeners: Array<() => void> = [];

  constructor(value: T) {
    this._value = value;
  }

  get value(): T {
    return this._value;
  }

  set value(value: T) {
    this._value = value;
    this.notifyListeners();
  }

  addListener(listener: () => void) {
    this.listeners.push(listener);
  }

  notifyListeners() {
    for (let listener of this.listeners) {
      listener();
    }
  }
}

使用範例

以獲取一個車輛詳情的場景來模擬網路請求和資料處理

1、ViewModel

import { Loaded, LoadError, Loading, LoadState, ValueNotifier } from './LoadState';

export class VehicleViewModel {
  lsVehicleDetail: ValueNotifier<LoadState | null>;

  constructor() {
    this.lsVehicleDetail = new ValueNotifier<LoadState | null>(null);
  }

  // 獲取車輛詳情
  async getVehicleDetail() {
    // 發起請求
    this.lsVehicleDetail.value = new Loading();

    await new Promise(resolve => setTimeout(resolve, 3000));

    // 獲得資料
    this.lsVehicleDetail.value = new Loaded("aa");

    await new Promise(resolve => setTimeout(resolve, 3000));

    // 模擬網路報錯
    this.lsVehicleDetail.value = new LoadError(123, "error");
  }
}

2、頁面處理

@Component
export struct VehicleComponent {
  private vm: VehicleViewModel = new VehicleViewModel();

  aboutToAppear() {

    this.vm.lsVehicleDetail.addListener(() => {
      this.vm.lsVehicleDetail.value?.loading(() => {
        // 開始網路請求
        console.log(`hello1:start Loading`);
      }).loaded((result) => {
        let data = result?.data() as String
        console.log(`hello2:${result} - ${data}`);
      }).loadError((error) => {
        console.log(`hello3:${error?.code} - ${error?.message}`);
      });
    });
  }
}

3、紀錄檔列印結果

hello1:start Loading
hello2:[object Object] - aa
hello3:123 - error