成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入淺出TypeScript在Model中的高級應用

開發 前端
在MVC、MVVC等前端經典常用開發模式中,V、C往往是重頭戲,可能是前端業務主要集中這兩塊。結合實際業務,筆者更喜歡路由模式、插件式設計,這種在迭代和維護上更能讓開發者受益。但我們今天來看看Model,看看M有什么擴展的可能。

[[330492]]

前言

在MVC、MVVC等前端經典常用開發模式中,V、C往往是重頭戲,可能是前端業務主要集中這兩塊。結合實際業務,筆者更喜歡路由模式、插件式設計,這種在迭代和維護上更能讓開發者受益(不過你需要找PM協調這事,畢竟他們理解的簡化用戶體驗,多半是怎么讓用戶操作簡單)。但我們今天來看看Model,看看M有什么擴展的可能。

背景

在讀到本文之前,你實際項目(如React+Redux)中請求服務器數據,可能是如下策略:

  1. componentDidMount 中發送redux action請求數據;
  2. 在action中發起異步網絡請求,當然你已經對網絡請求有一定封裝;
  3. 在網絡請求內部處理一定異常和邊際邏輯,然后返回請求到的數據;
  4. 拿到數據this.setState刷新頁面,同時可能存一份到全局redux中;

正常情況下,一個接口對應至少一個接口相應Model,萬一你還定義了接口請求的Model、一個頁面有5個接口呢?

如果項目已經引入TypeScript,結合編寫Model,你的編寫體驗肯定會如行云流水般一氣呵成!但實際開發中,你還需要對服務器返回的數據、頁面間傳遞的參數等涉及到數據傳遞的地方,做一些數據額外工作:

  • 對null、undefined等空值的異常處理(在ES最新方案和TS支持里,新增:鏈式調用?和運算符??,請讀者自行查詢使用手冊);
  • 對sex=0、1、2,time=1591509066等文案轉義;
  • (還有其他嗎?歡迎留言補充)

作為一個優秀且成熟的開發者,你肯定也已經做了上述額外的工作,在utils文件下編寫了幾十甚至上百的tool類函數,甚至還根據函數用途做了分類:時間類、年齡性別類、數字類、......,接著你在需要的地方import,然后你開始進行傳參調用。是的,一切看上去都很完美!

上面這個流程說的就是筆者本人,:)。

現況

隨著項目和業務的迭代,加上老板還是壓時間,最壞的情況是你遇到了并沒有遵守上述"開發規范"的同事,那結果只能是呵呵呵呵呵了。下面直接切入正題吧!

上述流程雖說有一定設計,但沒有做到高內聚、低耦合的原則,個人覺得不利于項目后期迭代和局部重構。

推薦另一個設計原則:面向對象五大原則SOLID[3]

下面舉個例子:

  • 接口里字段發生變更時,如性別從Sex改為Gender;
  • 前端內部重構,發現數據模型不匹配時,頁面C支持從頁面A附加參數a、或頁面B附加參數b跳入,重構后頁面B1附加參數b1也要跳轉C。從設計來說肯定是讓B1盡量按照以前B去適配時是最好的,否則C會越來越重。

上面提過不管是頁面交互,還是業務交互,最根本根本是數據的交換傳遞,從而去影響頁面和業務。數據就是串聯頁面和業務的核心,Model就是數據的表現形式。

再比如現在前后端分離的開發模式下,在需求確認后,開發需要做的第一件事是數據庫設計和接口設計,簡單的說就是字段的約定,然后在進行頁面開發,最終進行接口調試和系統調試,一直到交付測試。這期間,后端需要執行接口單元測試、前端需要Mock數據開發頁面。

如何解決

接口管理

目前筆記是通過JSON形式來進行接口管理,在項目初始化時,將配置的接口列表借助于 dva[4] 注冊到Redux Action中,然后接口調用就直接發送Action即可。最終到拿到服務器響應的Data。

接口配置(對應下面第二版):

  1. list: [ 
  2.   { 
  3.     alias: 'getCode'
  4.     apiPath: '/user/v1/getCode'
  5.     auth: false
  6.   }, 
  7.   { 
  8.     alias: 'userLogin'
  9.     apiPath: '/user/v1/userLogin'
  10.     auth: false
  11.     nextGeneral: 'saveUserInfo'
  12.   }, 
  13.   { 
  14.     alias: 'loginTokenByJVerify'
  15.     apiPath: '/user/v1/jgLoginApi'
  16.     auth: false
  17.     nextGeneral: 'saveUserInfo'
  18.   }, 

第一版:

  1. import { apiComm, apiMy } from 'services'
  2.  
  3. export default { 
  4.   namespace: 'bill'
  5.   state: {}, 
  6.   reducers: { 
  7.     updateState(state, { payload }) { 
  8.       return { ...state, ...payload }; 
  9.     }, 
  10.   }, 
  11.   effects: { 
  12.     *findBydoctorIdBill({ payload, callback }, { call }) { 
  13.       const res = yield call(apiMy.findBydoctorIdBill, payload); 
  14.       !apiComm.IsSuccess(res) && callback(res.data); 
  15.     }, 
  16.     *findByDoctorIdDetail({ payload, callback }, { call }) { 
  17.       const res = yield call(apiMy.findByDoctorIdDetail, payload); 
  18.       !apiComm.IsSuccess(res) && callback(res.data); 
  19.     }, 
  20.     *findStatementDetails({ payload, callback }, { call }) { 
  21.       const res = yield call(apiMy.findStatementDetails, payload); 
  22.       !apiComm.IsSuccess(res) && callback(res.data); 
  23.     }, 
  24.   }, 
  25. }; 

第二版使用高階函數,同時支持服務器地址切換,減少冗余代碼:

  1. export const connectModelService = (cfg: any = {}) => { 
  2.   const { apiBase = '', list = [] } = cfg; 
  3.   const listEffect = {}; 
  4.   list.forEach(kAlias => { 
  5.     const { alias, apiPath, nextGeneral, cbError = false, ...options } = kAlias; 
  6.     const effectAlias = function* da({ payload = {}, nextPage, callback }, { call, put }) { 
  7.       let apiBaseNew = apiBase; 
  8.       // apiBaseNew = urlApi; 
  9.       if (global.apiServer) { 
  10.         apiBaseNew = global.apiServer.indexOf('xxx.com') !== -1 ? global.apiServer : apiBase; 
  11.       } else if (!isDebug) { 
  12.         apiBaseNew = urlApi; 
  13.       } 
  14.       const urlpath = 
  15.         apiPath.indexOf('http://') === -1 && apiPath.indexOf('https://') === -1 ? `${apiBaseNew}${apiPath}` : apiPath; 
  16.       const res = yield call(hxRequest, urlpath, payload, options); 
  17.       const next = nextPage || nextGeneral; 
  18.       // console.log('=== hxRequest res'next, res); 
  19.       if (next) { 
  20.         yield put({ 
  21.           type: next
  22.           payload, 
  23.           res, 
  24.           callback, 
  25.         }); 
  26.       } else if (cbError) { 
  27.         callback && callback(res); 
  28.       } else { 
  29.         hasNoError(res) && callback && callback(res.data); 
  30.       } 
  31.     }; 
  32.     listEffect[alias] = effectAlias; 
  33.   }); 
  34.   return listEffect; 
  35. }; 

上面看上去還不錯,解決了接口地址管理、封裝了接口請求,但自己還得處理返回Data里的異常數據。

另外的問題是,接口和對應的請求與相應的數據Model并沒有對應起來,后面再次看代碼需要一段時間才能梳理業務邏輯。

請讀者思考一下上面的問題,然后繼續往下看。

Model管理

一個接口必然對應唯一一個請求Model和唯一一個響應Model。對,沒錯!下面利用此機制進一步討論。

所以通過響應Model去發起接口請求,在函數調用時也能利用請求Model判定入參合不合理,這樣就把主角從接口切換到Model了。這里個人覺得優先響應Model比較合適,更能直接明白這次請求后拿到的數據格式。

下面先看看通過Model發起請求的代碼:

  1. SimpleModel.get( 
  2.   { id: '1' }, 
  3.   { auth: false, onlyData: false }, 
  4. ).then((data: ResponseData<SimpleModel>) => 
  5.   setTimeout( 
  6.     () => 
  7.       console.log( 
  8.         '設置返回全部數據,返回 ResponseData<T> 或 ResponseData<T[]>'
  9.         typeof data, 
  10.         data, 
  11.       ), 
  12.     2000, 
  13.   ), 
  14. ); 

其中,SimpleModel是定義的響應Model,第一個參數是請求,第二個參數是請求配置項,接口地址被隱藏在SimpleModel內部了。

  1. import { Record } from 'immutable'
  2.  
  3. import { ApiOptons } from './Common'
  4. import { ServiceManager } from './Service'
  5.  
  6. /** 
  7.  * 簡單類型 
  8.  */ 
  9. const SimpleModelDefault = { 
  10.   a: 'test string'
  11.   sex: 0, 
  12. }; 
  13.  
  14. interface SimpleModelParams { 
  15.   id: string; 
  16.  
  17. export class SimpleModel extends Record(SimpleModelDefault) { 
  18.   static async get(params: SimpleModelParams, options?: ApiOptons) { 
  19.     return await ServiceManager.get<SimpleModel>( 
  20.       SimpleModel, 
  21.       'http://localhost:3000/test',   // 被隱藏的接口地址 
  22.       params, 
  23.       options, 
  24.     ); 
  25.   } 
  26.  
  27.   static sexMap = { 
  28.     0: '保密'
  29.     1: '男'
  30.     2: '女'
  31.   }; 
  32.  
  33.   sexText() { 
  34.     return SimpleModel.sexMap[this.sex] ?? '保密'
  35.   } 

這里借助了immutable里的Record[5],目的是將JSON Object反序列化為Class Object,目的是提高Model在項目中相關函數的內聚。更多介紹請看我另外一篇文章:JavaScript的強語言之路—另類的JSON序列化與反序列化[6]

  1. // utils/tool.tsx 
  2. export const sexMap = { 
  3.   0: '保密'
  4.   1: '男'
  5.   2: '女'
  6. }; 
  7.  
  8. export const sexText = (sex: number) => { 
  9.   return sexMap[sex] ?? '保密'
  10. }; 

直接在SimpleModel內部用this訪問具體數據,比調用utils/tool函數時傳入外部參數,更為內聚和方便維護。通過這種思路,相信你可以創造更多"黑魔法"的語法糖!

接著我們來看看 Common 文件內容:

  1. /** 
  2.  * 接口響應,最外層統一格式 
  3.  */ 
  4. export class ResponseData<T = any> { 
  5.   code? = 0; 
  6.   message? = '操作成功'
  7.   toastId? = -1; 
  8.   data?: T; 
  9.  
  10. /** 
  11.  * api配置信息 
  12.  */ 
  13. export class ApiOptons { 
  14.   headers?: any = {}; // 額外請求頭 
  15.   loading?: boolean = true; // 是否顯示loading 
  16.   loadingTime?: number = 2; // 顯示loading時間 
  17.   auth?: boolean = true; // 是否需要授權 
  18.   onlyData?: boolean = true; // 只返回data 
  19.  
  20. /** 
  21.  * 枚舉接口能返回的類型 
  22.  * - T、T[] 在 ApiOptons.onlyData 為true時是生效 
  23.  * - ResponseData<T>、ResponseData<T[]> 在 ApiOptons.onlyData 為false時是生效 
  24.  * - ResponseData 一般在接口內部發生異常時生效 
  25.  */ 
  26. export type ResultDataType<T> = 
  27.   | T 
  28.   | T[] 
  29.   | ResponseData<T> 
  30.   | ResponseData<T[]> 
  31.   | ResponseData; 

Service文件內部是封裝了axios:

  1. import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
  2. import { ApiOptons, ResponseData, ResultDataType } from './Common'
  3.  
  4. /** 
  5.  * 模擬UI loading 
  6.  */ 
  7. class Toast { 
  8.   static loading(txt: string, time: number = 3) { 
  9.     console.log(txt, time); 
  10.     return 1; 
  11.   } 
  12.   static info(txt: string, time: number = 3) { 
  13.     console.log(txt, time); 
  14.     return 1; 
  15.   } 
  16.   static remove(toastId: number) { 
  17.     console.log(toastId); 
  18.   } 
  19.  
  20. /** 
  21.  * 未知(默認)錯誤碼 
  22.  */ 
  23. const codeUnknownTask = -999; 
  24.  
  25. /** 
  26.  * 接口請求封裝基類 
  27.  */ 
  28. export class InterfaceService { 
  29.   /** 
  30.    * todo 
  31.    */ 
  32.   private static userProfile: { sysToken?: '' } = {}; 
  33.   public static setUser(_user: any) { 
  34.     InterfaceService.userProfile = _user; 
  35.   } 
  36.  
  37.   constructor(props: ApiOptons) { 
  38.     this.options = props; 
  39.   } 
  40.   /** 
  41.    * 默認配置 
  42.    */ 
  43.   public options = new ApiOptons(); 
  44.  
  45.   /** 
  46.    * todo 
  47.    */ 
  48.   public get sysToken(): string { 
  49.     return InterfaceService.userProfile?.sysToken ?? ''
  50.   } 
  51.  
  52.   /** 
  53.    * 構建header 
  54.    */ 
  55.   public get headers(): Object { 
  56.     return { 
  57.       Accept: 'application/json'
  58.       'Content-Type''application/json; charset=utf-8'
  59.       'app-info-key''xxx', // 自定義字段 
  60.     }; 
  61.   } 
  62.  
  63.   /** 
  64.    * 請求前置條件。可根據自己情況重構此函數 
  65.    */ 
  66.   preCheck() { 
  67.     if (this.options.loading && this.options.loadingTime > 0) { 
  68.       return Toast.loading('加載中...', this.options?.loadingTime ?? 3); 
  69.     } 
  70.     return -1; 
  71.   } 
  72.  
  73.   /** 
  74.    * 下載json,返回對象 
  75.    */ 
  76.   public static async getJSON(url: string) { 
  77.     try { 
  78.       const res = await fetch(url); 
  79.       return await res.json(); 
  80.     } catch (e) { 
  81.       console.log(e); 
  82.       return {}; 
  83.     } 
  84.   } 
  85.  
  86. /** 
  87.  * 接口請求封裝(axios版,也可以封裝其他版本的請求) 
  88.  */ 
  89. export class InterfaceAxios extends InterfaceService { 
  90.   constructor(props: ApiOptons) { 
  91.     super(props); 
  92.   } 
  93.  
  94.   /** 
  95.    * 封裝axios 
  96.    */ 
  97.   private request = (requestCfg: AxiosRequestConfig): Promise<ResponseData> => { 
  98.     return axios(requestCfg) 
  99.       .then(this.checkStatus) 
  100.       .catch((err: any) => { 
  101.         // 后臺接口異常,如接口不通、http狀態碼非200、data非json格式,判定為fatal錯誤 
  102.         console.log(requestCfg, err); 
  103.         return { 
  104.           code: 408, 
  105.           message: '網絡異常'
  106.         }; 
  107.       }); 
  108.   }; 
  109.  
  110.   /** 
  111.    * 檢查網絡響應狀態碼 
  112.    */ 
  113.   private checkStatus(response: AxiosResponse<ResponseData>) { 
  114.     if (response.status >= 200 && response.status < 300) { 
  115.       return response.data; 
  116.     } 
  117.     return { 
  118.       code: 408, 
  119.       message: '網絡數據異常'
  120.     }; 
  121.   } 
  122.  
  123.   /** 
  124.    * 發送POST請求 
  125.    */ 
  126.   public async post(url: string, data?: any) { 
  127.     const toastId = this.preCheck(); 
  128.     const ret = await this.request({ 
  129.       url, 
  130.       headers: this.headers, 
  131.       method: 'POST'
  132.       data: Object.assign({ sysToken: this.sysToken }, data), 
  133.     }); 
  134.     ret.toastId = toastId; 
  135.  
  136.     return ret; 
  137.   } 
  138.  
  139.   /** 
  140.    * 發送GET請求 
  141.    */ 
  142.   public async get(url: string, params?: any) { 
  143.     const toastId = this.preCheck(); 
  144.     const ret = await this.request({ 
  145.       url, 
  146.       headers: this.headers, 
  147.       method: 'GET'
  148.       params: Object.assign({ sysToken: this.sysToken }, params), 
  149.     }); 
  150.     ret.toastId = toastId; 
  151.     return ret; 
  152.   } 
  153.  
  154. export class ServiceManager { 
  155.   /** 
  156.    * 檢查接口數據 
  157.    */ 
  158.   public hasNoError(res: ResponseData) { 
  159.     if (res.toastId > 0) { 
  160.       Toast.remove(res.toastId); 
  161.     } 
  162.     if (res?.code !== 0 && res.code !== codeUnknownTask) { 
  163.       Toast.info(res?.message ?? '服務器出錯'); 
  164.       return false
  165.     } 
  166.     return true
  167.   } 
  168.  
  169.   /** 
  170.    * 解析響應 
  171.    */ 
  172.   public static parse<T>( 
  173.     modal: { new (x: any): T }, 
  174.     response: any
  175.     options: ApiOptons, 
  176.   ): ResultDataType<T> { 
  177.     if (!response || !response.data) { 
  178.       response.data = new modal({}); 
  179.     } else { 
  180.       if (response.data instanceof Array) { 
  181.         response.data = response.data.map((item: T) => new modal(item)); 
  182.       } else if (response.data instanceof Object) { 
  183.         response.data = new modal(response.data); 
  184.       } 
  185.       return options.onlyData ? response.data : response; 
  186.     } 
  187.   } 
  188.  
  189.   /** 
  190.    * post接口請求 
  191.    */ 
  192.   public static async post<T>( 
  193.     modal: { new (x: any): T }, 
  194.     url: string, 
  195.     body?: any
  196.     options: ApiOptons = new ApiOptons(), 
  197.   ): Promise<ResultDataType<T>> { 
  198.     // 使用合并,減少外部傳入配置 
  199.     options = Object.assign(new ApiOptons(), options); 
  200.  
  201.     const request = new InterfaceAxios(options); 
  202.     if (options.auth && !request.sysToken) { 
  203.       return { 
  204.         code: 403, 
  205.         message: '未授權'
  206.       }; 
  207.     } 
  208.  
  209.     try { 
  210.       const response = await request.post(url, body); 
  211.       return ServiceManager.parse<T>(modal, response, options); 
  212.     } catch (err) { 
  213.       // 記錄錯誤日志 
  214.       console.log(url, body, options, err); 
  215.       return { 
  216.         code: codeUnknownTask, 
  217.         message: '內部錯誤,請稍后再試'
  218.       }; 
  219.     } 
  220.   } 
  221.  
  222.   /** 
  223.    * get接口請求 
  224.    */ 
  225.   public static async get<T>( 
  226.     modal: { new (x: any): T }, 
  227.     url: string, 
  228.     params?: any
  229.     options: ApiOptons = new ApiOptons(), 
  230.   ): Promise<ResultDataType<T>> { 
  231.     // 使用合并,減少外部傳入配置 
  232.     options = Object.assign(new ApiOptons(), options); 
  233.  
  234.     const a = new InterfaceAxios(options); 
  235.     const request = new InterfaceAxios(options); 
  236.     if (options.auth && !request.sysToken) { 
  237.       return { 
  238.         code: 403, 
  239.         message: '未授權'
  240.       }; 
  241.     } 
  242.  
  243.     try { 
  244.       const response = await a.get(url, params); 
  245.       return ServiceManager.parse<T>(modal, response, options); 
  246.     } catch (err) { 
  247.       // 記錄錯誤日志 
  248.       console.log(url, params, options, err); 
  249.       return { 
  250.         code: codeUnknownTask, 
  251.         message: '內部錯誤,請稍后再試'
  252.       }; 
  253.     } 
  254.   } 

Service文件里內容有點長,主要有下面幾個類:

  • Toast:模擬請求接口時的loading,可通過接口調用時來配置;
  • InterfaceService:接口請求的基類,內部記錄當前用戶的Token、多環境服務器地址切換(代碼中未實現)、單次請求的接口配置、自定義Header、請求前的邏輯檢查、直接請求遠端JSON配置文件;
  • InterfaceAxios:繼承于InterfaceService,即axios版的接口請求,內部發起實際請求。你可以封裝fetch版本的。
  • ServiceManager:提供給Model使用的請求類,傳入響應Model和對應服務器地址后,等異步請求拿到數據后再將相應數據Data解析成對應的Model。

下面再貼一下完整的Model發起請求示例:

  1. import { ResponseData, ApiOptons, SimpleModel } from './model'
  2.  
  3. // 接口配置不同的三種請求 
  4. SimpleModel.get({ id: '1' }).then((data: ResponseData) => 
  5.   setTimeout( 
  6.     () => 
  7.       console.log( 
  8.         '因需授權導致內部異常,返回 ResponseData:'
  9.         typeof data, 
  10.         data, 
  11.       ), 
  12.     1000, 
  13.   ), 
  14. ); 
  15.  
  16. SimpleModel.get( 
  17.   { id: '1' }, 
  18.   { auth: false, onlyData: false }, 
  19. ).then((data: ResponseData<SimpleModel>) => 
  20.   setTimeout( 
  21.     () => 
  22.       console.log( 
  23.         '設置返回全部數據,返回 ResponseData<T> 或 ResponseData<T[]>'
  24.         typeof data, 
  25.         data, 
  26.       ), 
  27.     2000, 
  28.   ), 
  29. ); 
  30.  
  31. SimpleModel.get( 
  32.   { id: '1' }, 
  33.   { auth: false, onlyData: true }, 
  34. ).then((data: SimpleModel) => 
  35.   setTimeout( 
  36.     () => 
  37.       console.log( 
  38.         '僅返回關鍵數據data,返回 T 或 T[]:'
  39.         typeof data, 
  40.         data, 
  41.         data.sexText(), 
  42.       ), 
  43.     3000, 
  44.   ), 
  45. ); 

控制臺打印結果。注意,返回的 data 可能是JSON Object,也可能是 Immutable-js Record Object。

  1. 加載中... 2 
  2. 加載中... 2 
  3. 因需授權導致內部異常,返回 ResponseData:object { code: 403, message: '未授權' } 
  4. 設置返回全部數據,返回 ResponseData<T> 或 ResponseData<T[]> object { 
  5.   code: 0, 
  6.   message: '1'
  7.   data: SimpleModel { 
  8.     __ownerID: undefined, 
  9.     _values: List { 
  10.       size: 2, 
  11.       _origin: 0, 
  12.       _capacity: 2, 
  13.       _level: 5, 
  14.       _root: null
  15.       _tail: [VNode], 
  16.       __ownerID: undefined, 
  17.       __hash: undefined, 
  18.       __altered: false 
  19.     } 
  20.   }, 
  21.   toastId: 1 
  22. 僅返回關鍵數據data,返回 T 或 T[]:object SimpleModel { 
  23.   __ownerID: undefined, 
  24.   _values: List { 
  25.     size: 2, 
  26.     _origin: 0, 
  27.     _capacity: 2, 
  28.     _level: 5, 
  29.     _root: null
  30.     _tail: VNode { array: [Array], ownerID: OwnerID {} }, 
  31.     __ownerID: undefined, 
  32.     __hash: undefined, 
  33.     __altered: false 
  34.   } 
  35. } 男
最后再補充一個常見的復合類型Model示例:
  1. /** 
  2.  * 復雜類型 
  3.  */ 
  4.  
  5. const ComplexChildOneDefault = { 
  6.   name'lyc'
  7.   sex: 0, 
  8.   age: 18, 
  9. }; 
  10.  
  11. const ComplexChildTwoDefault = { 
  12.   count: 10, 
  13.   lastId: '20200607'
  14. }; 
  15.  
  16. const ComplexChildThirdDefault = { 
  17.   count: 10, 
  18.   lastId: '20200607'
  19. }; 
  20.  
  21. // const ComplexItemDefault = { 
  22. //   userNo: 'us1212'
  23. //   userProfile: ComplexChildOneDefault, 
  24. //   extraFirst: ComplexChildTwoDefault, 
  25. //   extraTwo: ComplexChildThirdDefault, 
  26. // }; 
  27.  
  28. // 復合類型建議使用class,而不是上面的object。因為object里不能添加可選屬性? 
  29. class ComplexItemDefault { 
  30.   userNo = 'us1212'
  31.   userProfile = ComplexChildOneDefault; 
  32.   extraFirst? = ComplexChildTwoDefault; 
  33.   extraSecond? = ComplexChildThirdDefault; 
  34.  
  35. // const ComplexListDefault = { 
  36. //   list: [], 
  37. //   pageNo: 1, 
  38. //   pageSize: 10, 
  39. //   pageTotal: 0, 
  40. // }; 
  41.  
  42. // 有數組的復合類型,如果要指定數組元素的Model,就必須用class 
  43. class ComplexListDefault { 
  44.   list: ComplexItemDefault[] = []; 
  45.   pageNo = 1; 
  46.   pageSize = 10; 
  47.   pageTotal = 0; 
  48.  
  49. interface ComplexModelParams { 
  50.   id: string; 
  51.  
  52. // 因為使用的class,所以需要 new 一個去初始化Record 
  53. export class ComplexModel extends Record(new ComplexListDefault()) { 
  54.   static async get(params: ComplexModelParams, options?: ApiOptons) { 
  55.     return await ServiceManager.get<ComplexModel>( 
  56.       ComplexModel, 
  57.       'http://localhost:3000/test2'
  58.       params, 
  59.       options, 
  60.     ); 
  61.   } 

下面是調用代碼:

  1. ComplexModel.get({ id: '2' }).then((data: ResponseData) => 
  2.   setTimeout( 
  3.     () => 
  4.       console.log( 
  5.         '因需授權導致內部異常,返回 ResponseData:'
  6.         typeof data, 
  7.         data, 
  8.       ), 
  9.     1000, 
  10.   ), 
  11. ); 
  12.  
  13. ComplexModel.get( 
  14.   { id: '2' }, 
  15.   { auth: false, onlyData: false }, 
  16. ).then((data: ResponseData<ComplexModel>) => 
  17.   setTimeout( 
  18.     () => 
  19.       console.log( 
  20.         '設置返回全部數據,返回 ResponseData<T> 或 ResponseData<T[]>'
  21.         typeof data, 
  22.         data.data.toJSON(), 
  23.       ), 
  24.     2000, 
  25.   ), 
  26. ); 
  27.  
  28. ComplexModel.get( 
  29.   { id: '2' }, 
  30.   { auth: false, onlyData: true }, 
  31. ).then((data: ComplexModel) => 
  32.   setTimeout( 
  33.     () => 
  34.       console.log( 
  35.         '僅返回關鍵數據data,返回 T 或 T[]:'
  36.         typeof data, 
  37.         data.toJSON(), 
  38.       ), 
  39.     3000, 
  40.   ), 
  41. ); 

接著是打印結果。這次Immutable-js Record Object就調用了data.toJSON()轉換成原始的JSON Object。

  1. 加載中... 2 
  2. 加載中... 2 
  3. 因需授權導致內部異常,返回 ResponseData:object { code: 403, message: '未授權' } 
  4. 設置返回全部數據,返回 ResponseData<T> 或 ResponseData<T[]> object { 
  5.   list: [ { userNo: '1', userProfile: [Object] } ], 
  6.   pageNo: 1, 
  7.   pageSize: 10, 
  8.   pageTotal: 0 
  9. 僅返回關鍵數據data,返回 T 或 T[]:object { 
  10.   list: [ { userNo: '1', userProfile: [Object] } ], 
  11.   pageNo: 1, 
  12.   pageSize: 10, 
  13.   pageTotal: 0 

總結

本文的代碼地址:
https://github.com/stelalae/node_demo,歡迎follow me~

現在接口調用是不是很優雅?!只關心請求和影響的數據格式,多使用高內聚低耦合,這對項目持續迭代非常有幫助的。使用TypeScript和Immutable-js來處理數據,在大型應用中越來越深入,從數據管理出發可以優化上層UI顯示和業務邏輯。

責任編輯:龐桂玉 來源: 今日頭條
相關推薦

2010-01-27 16:13:43

2018-12-19 14:40:08

Redis高級特性

2011-07-04 10:39:57

Web

2021-03-16 08:54:35

AQSAbstractQueJava

2022-08-02 07:56:53

反轉依賴反轉控制反轉

2023-05-05 18:33:15

2022-09-26 09:01:15

語言數據JavaScript

2022-03-23 18:58:11

ZookeeperZAB 協議

2009-11-30 16:46:29

學習Linux

2019-11-11 14:51:19

Java數據結構Properties

2022-12-02 09:13:28

SeataAT模式

2019-01-07 15:29:07

HadoopYarn架構調度器

2017-07-02 18:04:53

塊加密算法AES算法

2012-05-21 10:06:26

FrameworkCocoa

2021-07-20 15:20:02

FlatBuffers阿里云Java

2019-11-14 09:53:30

Set集合存儲

2009-12-25 15:49:43

Linux rescu

2023-12-04 13:22:00

JavaScript異步編程

2016-10-14 14:32:58

JavascriptDOMWeb

2009-11-17 17:31:58

Oracle COMM
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品国产成人国产三级 | 成人三级视频在线观看 | 成人精品福利 | 在线免费观看色 | 精品99在线 | 亚洲欧美激情精品一区二区 | 免费成人在线网站 | а天堂中文最新一区二区三区 | 午夜二区 | 99免费精品视频 | 一区二区三区四区在线视频 | 欧美日产国产成人免费图片 | 免费观看黄色一级片 | 成人午夜免费视频 | 国产激情福利 | 一区二区免费 | 久久综合久色欧美综合狠狠 | 亚洲国产欧美一区 | 国产精品一区二区三区在线 | 久久一级| 欧美成年黄网站色视频 | 欧美一区二区在线播放 | 欧美日韩视频在线 | 国产精品视频播放 | 日韩精品在线看 | 成人精品国产免费网站 | 免费观看的av毛片的网站 | 日韩一区二区在线视频 | 国产精品久久久久久久久久免费看 | 九九热精品在线 | 国偷自产av一区二区三区 | 午夜欧美 | 99re在线视频| 久草欧美视频 | 美女天堂 | 国产精品一区二区视频 | 神马久久久久久久久久 | 久久成人综合 | 欧美一级欧美三级在线观看 | 久久亚洲国产 | 亚洲国产精品一区二区久久 |