@angular前端項(xiàng)目代碼優(yōu)化:構(gòu)建Api Tree

前顏(yan)

在前端項(xiàng)目的開發(fā)過程中泼掠,往往后端會(huì)給到一份數(shù)據(jù)接口(本文簡稱api),為了減少后期的維護(hù)以及出錯(cuò)成本垦细,我的考慮是希望能夠找到這么一種方法择镇,可以將所有的api以某種方式統(tǒng)一的管理起來,并且很方便的進(jìn)行維護(hù)括改,比如當(dāng)后端修改了api名腻豌,我可以很快的定位到該api進(jìn)行修改,或者當(dāng)后端添加了新的api嘱能,我可以很快的知道具體是一個(gè)api寫漏了吝梅。
于是,我有了構(gòu)建Api Tree的想法焰檩。

一憔涉、前后端分離(Resful api)

在前后端分離的開發(fā)模式中订框,前后端的交互點(diǎn)主要在于各個(gè)數(shù)據(jù)接口析苫,也就是說后端把每個(gè)功能封裝成了api,供前端調(diào)用。
舉個(gè)例子衩侥,假設(shè)后端提供了關(guān)于user的以下3個(gè)api:

1 http(s)://www.xxx.com/api/v1/user/{ id }
2 http(s)://www.xxx.com/api/v1/user/getByName/{ name }
3 http(s)://www.xxx.com/api/v1/user/getByAge/{ age }

對應(yīng)的api描述如下(為了方便理解国旷,這里只考慮get請求):

 1 獲取用戶id的用戶數(shù)據(jù)
 2 獲取用戶名為name的用戶信息    
 3 獲取年齡為age的用戶列表

二、在Component中調(diào)用api接口獲取數(shù)據(jù)

目前各大前端框架比如angular茫死、vue以及react等跪但,都有提供相關(guān)HttpClient,用來發(fā)起http請求峦萎,比如get屡久、post、put爱榔、delete等被环,由于本人比較熟悉angular,下面代碼以angular進(jìn)行舉例(其他框架做法類似)详幽,代碼統(tǒng)一使用typescript語法筛欢。

在app.component.ts中調(diào)用api:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  userInfo;

  constructor(private http: HttpClient) {
    this.getUserById(1);
  }

  async getUserById(userId) {
    const url = `https://www.xxx.com/api/v1/user/${userId}`;
    this.userInfo = await this.http.get(url).toPromise();
  }

}

三、封裝UserHttpService

在項(xiàng)目中唇聘,由于多個(gè)頁面可能需要調(diào)用同一個(gè)api版姑,為了減少代碼的冗余以及方便維護(hù),比較好的方式是將所有的api封裝到一個(gè)Service中迟郎,然后將這個(gè)Service實(shí)例化成單例模式剥险,為所有的頁面提供http服務(wù)。
angular提供了依賴注入的功能宪肖,可以將Service注入到Module中炒嘲,并且在Module中的各個(gè)Component共享同一個(gè)Service,因此不需要手動(dòng)去實(shí)現(xiàn)Service的單例模式匈庭。
代碼如下:
user.http.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

const HOST_URL = `https://www.xxx.com/api/v1`;

@Injectable()
export class UserHttpService {

  constructor(private http: HttpClient) { }

  async getUserById(userId) {
    const url = `${HOST_URL}/user/${userId}`;
    return this.http.get(url).toPromise();
  }

  async getUserByName(name) {
    const url = `${HOST_URL}/user/getByName/${name}`;
    return this.http.get(url).toPromise();
  }

  async getUserByAge(age) {
    const url = `${HOST_URL}/user/getByAge/${age}`;
    return this.http.get(url).toPromise();
  }

}

app.component.ts

import { Component } from '@angular/core';
import { UserHttpService } from './user.http.service';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  constructor(private userHttpService: UserHttpService) {
    this.getUserById(1);
  }

  async getUserById(userId) {
    const userInfo = await this.userHttpService.getUserById(userId);
    console.log(userInfo);
  }

  async getUserByName(name) {
    const userInfo = await this.userHttpService.getUserByName(name);
    console.log(userInfo);
  }

  async getUserByAge(age) {
    const userInfoList = await this.userHttpService.getUserByAge(age);
    console.log(userInfoList);
  }

}

這樣的好處在于:
1夫凸、團(tuán)隊(duì)合作:
可以將前端項(xiàng)目分為HttpService層和Component層,由不同的人進(jìn)行分開維護(hù)
2阱持、減少代碼的冗余:
在多個(gè)Component中調(diào)用同一個(gè)api時(shí)夭拌,不需要寫多份代碼
3、降低維護(hù)和擴(kuò)展成本:
當(dāng)后端增加或修改接口時(shí)衷咽,由于所有的user api都在UserHttpService里鸽扁,所以能夠很容易的進(jìn)行接口調(diào)整,并且不影響Component層的代碼

但以上方案還存在一個(gè)缺點(diǎn)镶骗,即url使用字符串拼接的形式:

const url = `${HOST_URL}/user/getByName/${name}`;

這樣容易出現(xiàn)以下問題:
1桶现、接口名拼接出錯(cuò),并且由于是字符串拼接鼎姊,不會(huì)有語法提示(ts)
2骡和、沒有一份完整的映射后端的api表相赁,出現(xiàn)問題時(shí),不容易排查
因此慰于,接下來進(jìn)入本文的主題:構(gòu)建Api Tree钮科。

四、手動(dòng)構(gòu)建Api Tree

什么是Api Tree呢婆赠,我把它定義為將所有的api以節(jié)點(diǎn)的形式掛在一個(gè)樹上绵脯,最后形成了一棵包含所有api的樹形結(jié)構(gòu)。
對api tree的構(gòu)建初步想法(手動(dòng)構(gòu)建)如下:

/**
 * 手動(dòng)構(gòu)建 api tree 
 */
const APITREE = {
  domain1: {
    api: {
      v1: {
        user: {
          getByName: 'https://www.xxx.com/api/v1/user/getByName',
          getByAge: 'https://www.xxx.com/api/v1/user/getByAge'
        },
        animal: {
          getByType: 'https://www.xxx.com/api/v1/animal/getByType',
          getByAge: 'https://www.xxx.com/api/v1/animal/getByAge'
        }
      }
    }
  },
  domain2: {
    api: {
      car: {
        api1: 'https://xxx.xxx.cn/api/car/api1',
        api2: 'https://xxx.xxx.cn/api/car/api2'
      }
    }
  },
  domain3: {}
};
export { APITREE };

有了api tree休里,我們就可以采用如下方式來從api樹上摘取各個(gè)api節(jié)點(diǎn)的url蛆挫,代碼如下:

// 獲取url:https://www.xxx.com/api/v1/user/getByName
const getByNameUrl = APITREE.domain1.api.v1.user.getByName;

// 獲取url:https://xxx.xxx.cn/api/car/api1
const carApi1Url = APITREE.domain2.api.car.api1;

但是以上構(gòu)建api tree的方式存在兩個(gè)缺點(diǎn):
1、需要在各個(gè)節(jié)點(diǎn)手動(dòng)拼接全路徑
2妙黍、只能摘取子節(jié)點(diǎn)的url:getByName和getByAge
無法摘取父節(jié)點(diǎn)的url璃吧,比如我想獲取https://www.xxx.com/api/v1/user,無法通過APITREE.domain1.api.v1.user獲取

const APITREE = {
  domain1: {
    api: {
      v1: {
        // user為父節(jié)點(diǎn)
        // 缺點(diǎn)一:無法通過APITREE.domain1.api.v1.user獲取
        //        https://www.xxx.com/api/v1/user
        user: {
          // 缺點(diǎn)二:在getByName和getByAge節(jié)點(diǎn)中手動(dòng)寫入全路徑拼接
          getByName: 'https://www.xxx.com/api/v1/user/getByName',
          getByAge: 'https://www.xxx.com/api/v1/user/getByAge'
        }
      }
    }
  }
};

五废境、Api Tree生成器(ApiTreeGenerator)

針對手動(dòng)構(gòu)建Api Tree的問題畜挨,我引入了兩個(gè)概念:apiTreeConfig(基本配置)和apiTreeGenerator(生成器)。
通過apiTreeGenerator對apiTreeConfig進(jìn)行處理噩凹,最終生成真正的apiTree巴元。

1、apiTreeConfig我把它稱之為基本配置驮宴,apiTreeConfig具有一定的配置規(guī)則逮刨,要求每個(gè)節(jié)點(diǎn)名(除了域名)必須與api url中的每一節(jié)點(diǎn)名一致,因?yàn)閍piTreeGenerator是根據(jù)apiTreeConfig的各個(gè)節(jié)點(diǎn)名進(jìn)行生成堵泽,
api tree config配置如下:

/**
 * api tree config
 * _this可以省略不寫修己,但是不寫的話,在ts就沒有語法提示
 * 子節(jié)點(diǎn)getByName,getByAge以及_this可以為任意值迎罗,因?yàn)閷?huì)被apiTreeGenerator重新賦值
 */
const APITREECONFIG = {
  api: {
    v1: {
      user: {
        getByName: '',
        getByAge: '',
        _this: ''
      }
    },
    _this: ''
  }
 };

export { APITREECONFIG };

2睬愤、apiTreeGenerator我把它稱之為生成器,具有如下功能:
1) 遍歷apiTreeConfig纹安,處理apiTreeConfig的所有子節(jié)點(diǎn)尤辱,并根據(jù)該節(jié)點(diǎn)的所有父節(jié)點(diǎn)鏈生成完整的url,并且作為該節(jié)點(diǎn)的value厢岂,比如:
APITREECONFIG.api.v1.user.getByName -> https://www.xxx.com/api/v1/user/getByName
2) 遍歷apiTreeConfig光督,處理apiTreeConfig的所有父節(jié)點(diǎn),在每個(gè)父節(jié)點(diǎn)中添加_this子節(jié)點(diǎn)指向父節(jié)點(diǎn)的完整url塔粒。
apiTreeGenerator(生成器)的代碼如下:
(由于項(xiàng)目中只用到一個(gè)后端的數(shù)據(jù)结借,這里只實(shí)現(xiàn)了單域名的apiTreeGenerator,關(guān)于多域名的apiTreeGenerator卒茬,大家可以自行修改實(shí)現(xiàn)船老。)

import { APITREECONFIG } from './api-tree.config';

const APITREE = APITREECONFIG;
const HOST_URL = `https://www.xxx.com`;

/**
 * 為api node chain添加HOST_URL前綴
 */

const addHost = (apiNodeChain: string) => {
  return apiNodeChain ? `${HOST_URL}/${apiNodeChain.replace(/^\//, '')}` : HOST_URL;
};

/**
 * 根據(jù)api tree config 生成 api tree:
 * @param apiTreeConfig api tree config
 * @param parentApiNodeChain parentApiNode1/parentApiNode2/parentApiNode3
 */
const apiTreeGenerator = (apiTreeConfig: string | object, parentApiNodeChain?: string) => {
  for (const key of Object.keys(apiTreeConfig)) {
    const apiNode = key;
    const prefixChain = parentApiNodeChain ? `${parentApiNodeChain}/` : '';
    if (Object.prototype.toString.call(apiTreeConfig[key]) === '[object Object]') {
      apiTreeGenerator(apiTreeConfig[key], prefixChain + apiNode);
    } else {
      apiTreeConfig[key] = parentApiNodeChain
        ? addHost(prefixChain + apiTreeConfig[key])
        : addHost(apiTreeConfig[key]);
    }
  }
  // 創(chuàng)建_this節(jié)點(diǎn) (這里需要放在上面的for之后)
  apiTreeConfig['_this'] = parentApiNodeChain
    ? addHost(`${parentApiNodeChain}`)
    : addHost('');
};

apiTreeGenerator(APITREECONFIG);

export { APITREE };

結(jié)果:


image.png

優(yōu)化后的UserHttpService代碼如下:
user.http.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { APITREE } from './api-tree';

@Injectable()
export class UserHttpService {

  constructor(private http: HttpClient) { }

  async getUserById(userId) {
    const url = APITREE.api.v1.user._this + '/' + userId;
    return this.http.get(url).toPromise();
  }

  async getUserByName(name) {
    const url = APITREE.api.v1.user.getByName + '/' + name;
    return this.http.get(url).toPromise();
  }

  async getUserByAge(age) {
    const url = APITREE.api.v1.user.getByAge + '/' + age;
    return this.http.get(url).toPromise();
  }

}

六咖熟、總結(jié)

通過api tree,能帶來如下好處:
1努隙、能夠通過樹的形式來獲取api,關(guān)鍵是有語法提示
APITREE.api.v1.user.getByName
2辜昵、apiTreeConfig配置文件與后端的api接口一 一對應(yīng)荸镊,方便維護(hù)
3、當(dāng)后端修改api名時(shí)堪置,apiTreeConfig可以很方便的進(jìn)行調(diào)整

七躬存、demo

https://github.com/SimpleCodeCX/myCode/tree/master/angular/api-tree

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舀锨,隨后出現(xiàn)的幾起案子岭洲,更是在濱河造成了極大的恐慌,老刑警劉巖坎匿,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盾剩,死亡現(xiàn)場離奇詭異,居然都是意外死亡替蔬,警方通過查閱死者的電腦和手機(jī)告私,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來承桥,“玉大人驻粟,你說我怎么就攤上這事⌒滓欤” “怎么了蜀撑?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剩彬。 經(jīng)常有香客問我酷麦,道長,這世上最難降的妖魔是什么喉恋? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任贴铜,我火速辦了婚禮,結(jié)果婚禮上瀑晒,老公的妹妹穿的比我還像新娘绍坝。我一直安慰自己,他們只是感情好苔悦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布轩褐。 她就那樣靜靜地躺著,像睡著了一般玖详。 火紅的嫁衣襯著肌膚如雪把介。 梳的紋絲不亂的頭發(fā)上勤讽,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音拗踢,去河邊找鬼脚牍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛巢墅,可吹牛的內(nèi)容都是我干的诸狭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼君纫,長吁一口氣:“原來是場噩夢啊……” “哼驯遇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓄髓,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤叉庐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后会喝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陡叠,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年肢执,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匾竿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔚万,死狀恐怖岭妖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情反璃,我是刑警寧澤昵慌,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站淮蜈,受9級特大地震影響斋攀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梧田,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一淳蔼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裁眯,春花似錦鹉梨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逢艘,卻和暖如春旦袋,著一層夾襖步出監(jiān)牢的瞬間骤菠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工疤孕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留商乎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓祭阀,卻偏偏與公主長得像鹉戚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子柬讨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • core package 概要:Core是所有其他包的基礎(chǔ)包.它提供了大部分功能包括metadata崩瓤,templa...
    LOVE小狼閱讀 2,559評論 0 3
  • 去年有段時(shí)間得空袍啡,就把谷歌GAE的API權(quán)威指南看了一遍踩官,收獲頗豐,特別是在自己幾乎獨(dú)立開發(fā)了公司的云數(shù)據(jù)中心之后...
    騎單車的勛爵閱讀 20,475評論 0 41
  • 自卑的心理是很多人都有的境输,自卑給我們的生活帶來很嚴(yán)重的問題蔗牡,自卑不僅能影響我們的愛情甚至是事業(yè)。我曾經(jīng)也...
    韓影峰閱讀 138評論 0 2
  • 我喜歡安靜嗅剖,每次下班回去后辩越,獨(dú)處于臥室,享受著寧靜帶來的平穩(wěn)信粮。我更習(xí)慣安靜黔攒,在安靜中的環(huán)境中,我可以去緩慢思...
    義馬四小呂宇川閱讀 190評論 2 3
  • 2018年7月14日 星期6 天氣晴熱 假期已經(jīng)過了八天了强缘,若寧的學(xué)習(xí)之路一天也沒有清閑過督惰,相當(dāng)...
    一個(gè)小老漢閱讀 114評論 0 0