自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入淺出TypeScript在Model中的高級應(yīng)用

開發(fā) 前端
在MVC、MVVC等前端經(jīng)典常用開發(fā)模式中,V、C往往是重頭戲,可能是前端業(yè)務(wù)主要集中這兩塊。結(jié)合實(shí)際業(yè)務(wù),筆者更喜歡路由模式、插件式設(shè)計(jì),這種在迭代和維護(hù)上更能讓開發(fā)者受益。但我們今天來看看Model,看看M有什么擴(kuò)展的可能。

[[330492]]

前言

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

背景

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

  1. componentDidMount 中發(fā)送redux action請求數(shù)據(jù);
  2. 在action中發(fā)起異步網(wǎng)絡(luò)請求,當(dāng)然你已經(jīng)對網(wǎng)絡(luò)請求有一定封裝;
  3. 在網(wǎng)絡(luò)請求內(nèi)部處理一定異常和邊際邏輯,然后返回請求到的數(shù)據(jù);
  4. 拿到數(shù)據(jù)this.setState刷新頁面,同時可能存一份到全局redux中;

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

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

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

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

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

現(xiàn)況

隨著項(xiàng)目和業(yè)務(wù)的迭代,加上老板還是壓時間,最壞的情況是你遇到了并沒有遵守上述"開發(fā)規(guī)范"的同事,那結(jié)果只能是呵呵呵呵呵了。下面直接切入正題吧!

上述流程雖說有一定設(shè)計(jì),但沒有做到高內(nèi)聚、低耦合的原則,個人覺得不利于項(xiàng)目后期迭代和局部重構(gòu)。

推薦另一個設(shè)計(jì)原則:面向?qū)ο笪宕笤瓌tSOLID[3]

下面舉個例子:

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

上面提過不管是頁面交互,還是業(yè)務(wù)交互,最根本根本是數(shù)據(jù)的交換傳遞,從而去影響頁面和業(yè)務(wù)。數(shù)據(jù)就是串聯(lián)頁面和業(yè)務(wù)的核心,Model就是數(shù)據(jù)的表現(xiàn)形式。

再比如現(xiàn)在前后端分離的開發(fā)模式下,在需求確認(rèn)后,開發(fā)需要做的第一件事是數(shù)據(jù)庫設(shè)計(jì)和接口設(shè)計(jì),簡單的說就是字段的約定,然后在進(jìn)行頁面開發(fā),最終進(jìn)行接口調(diào)試和系統(tǒng)調(diào)試,一直到交付測試。這期間,后端需要執(zhí)行接口單元測試、前端需要Mock數(shù)據(jù)開發(fā)頁面。

如何解決

接口管理

目前筆記是通過JSON形式來進(jìn)行接口管理,在項(xiàng)目初始化時,將配置的接口列表借助于 dva[4] 注冊到Redux Action中,然后接口調(diào)用就直接發(fā)送Action即可。最終到拿到服務(wù)器響應(yīng)的Data。

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

  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. }; 

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

  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里的異常數(shù)據(jù)。

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

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

Model管理

一個接口必然對應(yīng)唯一一個請求Model和唯一一個響應(yīng)Model。對,沒錯!下面利用此機(jī)制進(jìn)一步討論。

所以通過響應(yīng)Model去發(fā)起接口請求,在函數(shù)調(diào)用時也能利用請求Model判定入?yún)⒑喜缓侠恚@樣就把主角從接口切換到Model了。這里個人覺得優(yōu)先響應(yīng)Model比較合適,更能直接明白這次請求后拿到的數(shù)據(jù)格式。

下面先看看通過Model發(fā)起請求的代碼:

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

其中,SimpleModel是定義的響應(yīng)Model,第一個參數(shù)是請求,第二個參數(shù)是請求配置項(xiàng),接口地址被隱藏在SimpleModel內(nèi)部了。

  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,目的是提高M(jìn)odel在項(xiàng)目中相關(guān)函數(shù)的內(nèi)聚。更多介紹請看我另外一篇文章:JavaScript的強(qiáng)語言之路—另類的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內(nèi)部用this訪問具體數(shù)據(jù),比調(diào)用utils/tool函數(shù)時傳入外部參數(shù),更為內(nèi)聚和方便維護(hù)。通過這種思路,相信你可以創(chuàng)造更多"黑魔法"的語法糖!

接著我們來看看 Common 文件內(nèi)容:

  1. /** 
  2.  * 接口響應(yīng),最外層統(tǒng)一格式 
  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; // 是否需要授權(quán) 
  18.   onlyData?: boolean = true; // 只返回data 
  19.  
  20. /** 
  21.  * 枚舉接口能返回的類型 
  22.  * - T、T[] 在 ApiOptons.onlyData 為true時是生效 
  23.  * - ResponseData<T>、ResponseData<T[]> 在 ApiOptons.onlyData 為false時是生效 
  24.  * - ResponseData 一般在接口內(nèi)部發(fā)生異常時生效 
  25.  */ 
  26. export type ResultDataType<T> = 
  27.   | T 
  28.   | T[] 
  29.   | ResponseData<T> 
  30.   | ResponseData<T[]> 
  31.   | ResponseData; 

Service文件內(nèi)部是封裝了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.  * 未知(默認(rèn))錯誤碼 
  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.    * 默認(rèn)配置 
  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.    * 構(gòu)建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.    * 請求前置條件??筛鶕?jù)自己情況重構(gòu)此函數(shù) 
  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狀態(tài)碼非200、data非json格式,判定為fatal錯誤 
  102.         console.log(requestCfg, err); 
  103.         return { 
  104.           code: 408, 
  105.           message: '網(wǎng)絡(luò)異常'
  106.         }; 
  107.       }); 
  108.   }; 
  109.  
  110.   /** 
  111.    * 檢查網(wǎng)絡(luò)響應(yīng)狀態(tài)碼 
  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: '網(wǎng)絡(luò)數(shù)據(jù)異常'
  120.     }; 
  121.   } 
  122.  
  123.   /** 
  124.    * 發(fā)送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.    * 發(fā)送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.    * 檢查接口數(shù)據(jù) 
  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 ?? '服務(wù)器出錯'); 
  164.       return false
  165.     } 
  166.     return true
  167.   } 
  168.  
  169.   /** 
  170.    * 解析響應(yīng) 
  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: '未授權(quán)'
  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: '內(nèi)部錯誤,請稍后再試'
  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: '未授權(quán)'
  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: '內(nèi)部錯誤,請稍后再試'
  252.       }; 
  253.     } 
  254.   } 

Service文件里內(nèi)容有點(diǎn)長,主要有下面幾個類:

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

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

  1. import { ResponseData, ApiOptons, SimpleModel } from './model'
  2.  
  3. // 接口配置不同的三種請求 
  4. SimpleModel.get({ id: '1' }).then((data: ResponseData) => 
  5.   setTimeout( 
  6.     () => 
  7.       console.log( 
  8.         '因需授權(quán)導(dǎo)致內(nèi)部異常,返回 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.         '設(shè)置返回全部數(shù)據(jù),返回 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.         '僅返回關(guān)鍵數(shù)據(jù)data,返回 T 或 T[]:'
  39.         typeof data, 
  40.         data, 
  41.         data.sexText(), 
  42.       ), 
  43.     3000, 
  44.   ), 
  45. ); 

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

  1. 加載中... 2 
  2. 加載中... 2 
  3. 因需授權(quán)導(dǎo)致內(nèi)部異常,返回 ResponseData:object { code: 403, message: '未授權(quán)' } 
  4. 設(shè)置返回全部數(shù)據(jù),返回 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. 僅返回關(guān)鍵數(shù)據(jù)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. } 男
最后再補(bǔ)充一個常見的復(fù)合類型Model示例:
  1. /** 
  2.  * 復(fù)雜類型 
  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. // 復(fù)合類型建議使用class,而不是上面的object。因?yàn)閛bject里不能添加可選屬性? 
  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. // 有數(shù)組的復(fù)合類型,如果要指定數(shù)組元素的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. // 因?yàn)槭褂玫腸lass,所以需要 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.   } 

下面是調(diào)用代碼:

  1. ComplexModel.get({ id: '2' }).then((data: ResponseData) => 
  2.   setTimeout( 
  3.     () => 
  4.       console.log( 
  5.         '因需授權(quán)導(dǎo)致內(nèi)部異常,返回 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.         '設(shè)置返回全部數(shù)據(jù),返回 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.         '僅返回關(guān)鍵數(shù)據(jù)data,返回 T 或 T[]:'
  36.         typeof data, 
  37.         data.toJSON(), 
  38.       ), 
  39.     3000, 
  40.   ), 
  41. ); 

接著是打印結(jié)果。這次Immutable-js Record Object就調(diào)用了data.toJSON()轉(zhuǎn)換成原始的JSON Object。

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

總結(jié)

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

現(xiàn)在接口調(diào)用是不是很優(yōu)雅?!只關(guān)心請求和影響的數(shù)據(jù)格式,多使用高內(nèi)聚低耦合,這對項(xiàng)目持續(xù)迭代非常有幫助的。使用TypeScript和Immutable-js來處理數(shù)據(jù),在大型應(yīng)用中越來越深入,從數(shù)據(jù)管理出發(fā)可以優(yōu)化上層UI顯示和業(yè)務(wù)邏輯。

責(zé)任編輯:龐桂玉 來源: 今日頭條
相關(guān)推薦

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

反轉(zhuǎn)依賴反轉(zhuǎn)控制反轉(zhuǎn)

2023-05-05 18:33:15

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2022-03-23 18:58:11

ZookeeperZAB 協(xié)議

2019-11-11 14:51:19

Java數(shù)據(jù)結(jié)構(gòu)Properties

2022-12-02 09:13:28

SeataAT模式

2009-11-30 16:46:29

學(xué)習(xí)Linux

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2019-11-14 09:53:30

Set集合存儲

2009-12-25 15:49:43

Linux rescu

2009-11-17 17:31:58

Oracle COMM

2021-07-19 11:54:15

MySQL優(yōu)先隊(duì)列

2023-12-04 13:22:00

JavaScript異步編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號