前顏(yan)
成都網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、成都小程序開(kāi)發(fā)、集團(tuán)成都定制網(wǎng)站等服務(wù)項(xiàng)目。核心團(tuán)隊(duì)均擁有互聯(lián)網(wǎng)行業(yè)多年經(jīng)驗(yàn),服務(wù)眾多知名企業(yè)客戶;涵蓋的客戶類型包括:成都崗?fù)?/a>等眾多領(lǐng)域,積累了大量豐富的經(jīng)驗(yàn),同時(shí)也獲得了客戶的一致贊賞!
在前端項(xiàng)目的開(kāi)發(fā)過(guò)程中,往往后端會(huì)給到一份數(shù)據(jù)接口(本文簡(jiǎn)稱api),為了減少后期的維護(hù)以及出錯(cuò)成本,我的考慮是希望能夠找到這么一種方法,可以將所有的api以某種方式統(tǒng)一的管理起來(lái),并且很方便的進(jìn)行維護(hù),比如當(dāng)后端修改了api名,我可以很快的定位到該api進(jìn)行修改,或者當(dāng)后端添加了新的api,我可以很快的知道具體是一個(gè)api寫漏了。
于是,我有了構(gòu)建Api Tree的想法。
一、前后端分離(Resful api)
在前后端分離的開(kāi)發(fā)模式中,前后端的交互點(diǎn)主要在于各個(gè)數(shù)據(jù)接口,也就是說(shuō)后端把每個(gè)功能封裝成了api,供前端調(diào)用。
舉個(gè)例子,假設(shè)后端提供了關(guān)于user的以下3個(gè)api:
http(s)://www.xxx.com/api/v1/user/{ id } http(s)://www.xxx.com/api/v1/user/getByName/{ name } http(s)://www.xxx.com/api/v1/user/getByAge/{ age }
對(duì)應(yīng)的api描述如下(為了方便理解,這里只考慮get請(qǐng)求):
1 獲取用戶id的用戶數(shù)據(jù)
2 獲取用戶名為name的用戶信息
3 獲取年齡為age的用戶列表
二、在Component中調(diào)用api接口獲取數(shù)據(jù)
目前各大前端框架比如angular、vue以及react等,都有提供相關(guān)HttpClient,用來(lái)發(fā)起http請(qǐng)求,比如get、post、put、delete等,由于本人比較熟悉angular,下面代碼以angular進(jìn)行舉例(其他框架做法類似),代碼統(tǒng)一使用typescript語(yǔ)法。
在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è)頁(yè)面可能需要調(diào)用同一個(gè)api,為了減少代碼的冗余以及方便維護(hù),比較好的方式是將所有的api封裝到一個(gè)Service中,然后將這個(gè)Service實(shí)例化成單例模式,為所有的頁(yè)面提供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)行分開(kāi)維護(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)以下問(wèn)題:
1、接口名拼接出錯(cuò),并且由于是字符串拼接,不會(huì)有語(yǔ)法提示(ts)
2、沒(méi)有一份完整的映射后端的api表,出現(xiàn)問(wèn)題時(shí),不容易排查 因此,接下來(lái)進(jìn)入本文的主題:構(gòu)建Api Tree。
四、手動(dòng)構(gòu)建Api Tree
什么是Api Tree呢,我把它定義為將所有的api以節(jié)點(diǎn)的形式掛在一個(gè)樹(shù)上,最后形成了一棵包含所有api的樹(shù)形結(jié)構(gòu)。
對(duì)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,我們就可以采用如下方式來(lái)從api樹(shù)上摘取各個(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,無(wú)法摘取父節(jié)點(diǎn)的url,比如我想獲取 https://www.xxx.com/api/v1/user ,無(wú)法通過(guò) APITREE.domain1.api.v1.user 獲取
const APITREE = { domain1: { api: { v1: { // user為父節(jié)點(diǎn) // 缺點(diǎn)一:無(wú)法通過(guò)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)
針對(duì)手動(dòng)構(gòu)建Api Tree的問(wèn)題,我引入了兩個(gè)概念:apiTreeConfig(基本配置)和apiTreeGenerator(生成器)。
通過(guò)apiTreeGenerator對(duì)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就沒(méi)有語(yǔ)法提示 * 子節(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é)果:
優(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é)
通過(guò)api tree,能帶來(lái)如下好處:
1、能夠通過(guò)樹(shù)的形式來(lái)獲取api,關(guān)鍵是有語(yǔ)法提示
APITREE.api.v1.user.getByName
2、apiTreeConfig配置文件與后端的api接口一 一對(duì)應(yīng),方便維護(hù)
3、當(dāng)后端修改api名時(shí),apiTreeConfig可以很方便的進(jìn)行調(diào)整
七、demo
github代碼:https://github.com/SimpleCodeCX/myCode/tree/master/angular/api-tree
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
網(wǎng)站欄目:@angular前端項(xiàng)目代碼優(yōu)化之構(gòu)建ApiTree的方法
本文網(wǎng)址:http://muchs.cn/article8/ihjsop.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營(yíng)銷推廣、自適應(yīng)網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、靜態(tài)網(wǎng)站、面包屑導(dǎo)航、網(wǎng)頁(yè)設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)