又一輪子?Typescript+React+Redux,放棄saga,支持服務器渲染同構

  • 你是原生Redux用戶?有沒有以爲寫Redux太繁瑣了?
  • 你是dvaJS用戶?有沒有以爲redux-saga概念太多,且yield沒法返回TS類型?

試試react-coat吧:

項目地址:https://github.com/wooline/react-coat前端

// 僅需一個類,搞定 action、reducer、effect、loading
class ModuleHandlers extends BaseModuleHandlers {
  @reducer
  protected putCurUser(curUser: CurUser): State {
    return {...this.state, curUser};
  }
  @reducer
  public putShowLoginPop(showLoginPop: boolean): State {
    return {...this.state, showLoginPop};
  }
  @effect("login") // 使用自定義loading狀態
  public async login(payload: {username: string; password: string}) {
    const loginResult = await sessionService.api.login(payload);
    if (!loginResult.error) {
      this.updateState({curUser: loginResult.data});
      Toast.success("歡迎您回來!");
    } else {
      alert(loginResult.error.message);
    }
  }
  // uncatched錯誤會觸發@@framework/ERROR,監聽併發送給後臺
  @effect(null) // 不須要loading,設置爲null
  protected async ["@@framework/ERROR"](error: CustomError) {
    if (error.code === "401") {
      this.dispatch(this.actions.putShowLoginPop(true));
    } else if (error.code === "301" || error.code === "302") {
      this.dispatch(this.routerActions.replace(error.detail));
    } else {
      Toast.fail(error.message);
      await settingsService.api.reportError(error);
    }
  }
  // 監聽自已的INIT Action,作一些異步數據請求
  @effect()
  protected async ["app/INIT"]() {
    const [projectConfig, curUser] = await Promise.all([
      settingsService.api.getSettings(),
      sessionService.api.getCurUser()
    ]);
    this.updateState({
      projectConfig,
      curUser,
    });
  }
}

react-coat 特色

  • 集成 react、redux、react-router、history 等相關框架
  • 僅爲以上框架的糖衣外套,不改變其基本概念,無強侵入與破壞性
  • 結構化前端工程、業務模塊化,支持按需加載
  • 同時支持 SPA(單頁應用)和 SSR(服務器渲染)
  • 使用 typescript 嚴格類型,更好的靜態檢查與智能提示
  • 開源微框架,源碼不到千行,幾乎不用學習便可上手

與 Dva 的異同

  • 引入 ActionHandler 觀察者模式,更優雅的處理模塊之間的協做
  • 去除 redux-saga,使用 async、await 替代,簡化代碼的同時對 TS 類型支持更全面
  • 原生使用 typescript 組織和開發,更全面的類型安全
  • 路由組件化、無 Page 概念、更天然的 API 和更簡單的組織結構
  • 更大的靈活性和自由度,不強封裝腳手架等
  • 支持 SPA(單頁應用)和 SSR(服務器渲染)快速切換,
  • 支持模塊異步按需加載和同步加載快速切換
差別示例:使用強類型組織全部 reducer 和 effect
// Dva中常這樣寫
dispatch({ type: 'moduleA/query', payload:{username:"jimmy"}} })

//本框架中可直接利用ts類型反射和檢查:
this.dispatch(moduleA.actions.query({username:"jimmy"}))
差別示例:State 和 Actions 支持繼承
// Dva不支持繼承

// 本框架能夠直接繼承

class ModuleHandlers extends ArticleHandlers<State, PhotoResource> {
  constructor() {
    super({}, {api});
  }
  @effect()
  protected async parseRouter() {
    const result = await super.parseRouter();
    this.dispatch(this.actions.putRouteData({showComment: true}));
    return result;
  }
  @effect()
  protected async [ModuleNames.photos + "/INIT"]() {
    await super.onInit();
  }
}
差別示例:在 Dva 中,由於使用 redux-saga,假設在一個 effect 中使用 yield put 派發一個 action,以此來調用另外一個 effect,雖然 yield 能夠等待 action 的派發,但並不能等待後續 effect 的處理:
// 在Dva中,updateState並不會等待otherModule/query的effect處理完畢了才執行
effects: {
    * query (){
        yield put({type: 'otherModule/query',payload:1});
        yield put({type: 'updateState',  payload: 2});
    }
}

// 在本框架中,可以使用awiat關鍵字, updateState 會等待otherModule/query的effect處理完畢了才執行
class ModuleHandlers {
    async query (){
        await this.dispatch(otherModule.actions.query(1));
        this.dispatch(thisModule.actions.updateState(2));
    }
}
差別示例:若是 ModuleA 進行某項操做成功以後,ModuleB 或 ModuleC 都須要 update 自已的 State,因爲缺乏 action 的觀察者模式,因此只能將 ModuleB 或 ModuleC 的刷新動做寫死在 ModuleA 中:
// 在Dva中須要主動Put調用ModuleB或ModuleC的Action
effects: {
    * update (){
        ...
        if(callbackModuleName==="ModuleB"){
          yield put({type: 'ModuleB/update',payload:1});
        }else if(callbackModuleName==="ModuleC"){
          yield put({type: 'ModuleC/update',payload:1});
        }
    }
}

// 在本框架中,可以使用ActionHandler觀察者模式:
class ModuleB {
    //在ModuleB中兼聽"ModuleA/update" action
    async ["ModuleA/update"] (){
        ....
    }
}

class ModuleC {
    //在ModuleC中兼聽"ModuleA/update" action
    async ["ModuleA/update"] (){
        ....
    }
}

遵循規則:

  • M 和 V 之間使用單向數據流
  • 整站保持單個 Store
  • Store 爲 Immutability 不可變數據
  • 改變 Store 數據,必須經過 Reducer
  • 調用 Reducer 必須經過顯式的 dispatch Action
  • Reducer 必須爲 pure function 純函數
  • 有反作用的行爲,所有放到 Effect 函數中
  • 每一個 reducer 只能修改 Store 下的某個節點,但能夠讀取全部節點
  • 路由組件化,不使用集中式配置

快速上手及 Demo

本框架上手簡單react

相關文章
相關標籤/搜索