@angular前端项目代码优化之构建Api Tree的方法


Posted in Javascript onDecember 24, 2018

前颜(yan)

在前端项目的开发过程中,往往后端会给到一份数据接口(本文简称api),为了减少后期的维护以及出错成本,我的考虑是希望能够找到这么一种方法,可以将所有的api以某种方式统一的管理起来,并且很方便的进行维护,比如当后端修改了api名,我可以很快的定位到该api进行修改,或者当后端添加了新的api,我可以很快的知道具体是一个api写漏了。

于是,我有了构建Api Tree的想法。

一、前后端分离(Resful api)

在前后端分离的开发模式中,前后端的交互点主要在于各个数据接口,也就是说后端把每个功能封装成了api,供前端调用。

举个例子,假设后端提供了关于user的以下3个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 }

对应的api描述如下(为了方便理解,这里只考虑get请求):

1 获取用户id的用户数据
2 获取用户名为name的用户信息    
3 获取年龄为age的用户列表

二、在Component中调用api接口获取数据

目前各大前端框架比如angular、vue以及react等,都有提供相关HttpClient,用来发起http请求,比如get、post、put、delete等,由于本人比较熟悉angular,下面代码以angular进行举例(其他框架做法类似),代码统一使用typescript语法。

在app.component.ts中调用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

在项目中,由于多个页面可能需要调用同一个api,为了减少代码的冗余以及方便维护,比较好的方式是将所有的api封装到一个Service中,然后将这个Service实例化成单例模式,为所有的页面提供http服务。

angular提供了依赖注入的功能,可以将Service注入到Module中,并且在Module中的各个Component共享同一个Service,因此不需要手动去实现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、团队合作:

可以将前端项目分为HttpService层和Component层,由不同的人进行分开维护

2、减少代码的冗余:

在多个Component中调用同一个api时,不需要写多份代码

3、降低维护和扩展成本:

当后端增加或修改接口时,由于所有的user api都在UserHttpService里,所以能够很容易的进行接口调整,并且不影响Component层的代码

但以上方案还存在一个缺点,即url使用字符串拼接的形式:

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

这样容易出现以下问题:

1、接口名拼接出错,并且由于是字符串拼接,不会有语法提示(ts)

2、没有一份完整的映射后端的api表,出现问题时,不容易排查 因此,接下来进入本文的主题:构建Api Tree。

四、手动构建Api Tree

什么是Api Tree呢,我把它定义为将所有的api以节点的形式挂在一个树上,最后形成了一棵包含所有api的树形结构。

对api tree的构建初步想法(手动构建)如下:

/**
 * 手动构建 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树上摘取各个api节点的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;

但是以上构建api tree的方式存在两个缺点:

1、需要在各个节点手动拼接全路径

2、只能摘取子节点的url:getByName和getByAge,无法摘取父节点的url,比如我想获取 https://www.xxx.com/api/v1/user ,无法通过 APITREE.domain1.api.v1.user 获取

const APITREE = {
 domain1: {
  api: {
   v1: {
    // user为父节点
    // 缺点一:无法通过APITREE.domain1.api.v1.user获取
    //    https://www.xxx.com/api/v1/user
    user: {
     // 缺点二:在getByName和getByAge节点中手动写入全路径拼接
     getByName: 'https://www.xxx.com/api/v1/user/getByName',
     getByAge: 'https://www.xxx.com/api/v1/user/getByAge'
    }
   }
  }
 }
};

五、Api Tree生成器(ApiTreeGenerator)

针对手动构建Api Tree的问题,我引入了两个概念:apiTreeConfig(基本配置)和apiTreeGenerator(生成器)。

通过apiTreeGenerator对apiTreeConfig进行处理,最终生成真正的apiTree。

1、apiTreeConfig我把它称之为基本配置,apiTreeConfig具有一定的配置规则,要求每个节点名(除了域名)必须与api url中的每一节点名一致,因为apiTreeGenerator是根据apiTreeConfig的各个节点名进行生成, api tree config配置如下:

/**
 * api tree config
 * _this可以省略不写,但是不写的话,在ts就没有语法提示
 * 子节点getByName,getByAge以及_this可以为任意值,因为将会被apiTreeGenerator重新赋值
 */
const APITREECONFIG = {
 api: {
  v1: {
   user: {
    getByName: '',
    getByAge: '',
    _this: ''
   }
  },
  _this: ''
 }
 };

export { APITREECONFIG };

2、apiTreeGenerator我把它称之为生成器,具有如下功能:

1) 遍历apiTreeConfig,处理apiTreeConfig的所有子节点,并根据该节点的所有父节点链生成完整的url,并且作为该节点的value,比如: APITREECONFIG.api.v1.user.getByName -> https://www.xxx.com/api/v1/user/getByName

2) 遍历apiTreeConfig,处理apiTreeConfig的所有父节点,在每个父节点中添加_this子节点指向父节点的完整url。

apiTreeGenerator(生成器)的代码如下:

(由于项目中只用到一个后端的数据,这里只实现了单域名的apiTreeGenerator,关于多域名的apiTreeGenerator,大家可以自行修改实现。)

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

/**
 * 根据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]);
  }
 }
 // 创建_this节点 (这里需要放在上面的for之后)
 apiTreeConfig['_this'] = parentApiNodeChain
  ? addHost(`${parentApiNodeChain}`)
  : addHost('');
};

apiTreeGenerator(APITREECONFIG);

export { APITREE };

结果:

@angular前端项目代码优化之构建Api Tree的方法

优化后的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();
 }

}

六、总结

通过api tree,能带来如下好处:

1、能够通过树的形式来获取api,关键是有语法提示
APITREE.api.v1.user.getByName

2、apiTreeConfig配置文件与后端的api接口一 一对应,方便维护

3、当后端修改api名时,apiTreeConfig可以很方便的进行调整

七、demo

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
各情景下元素宽高的获取实现代码
Sep 13 Javascript
使用jQuery同时控制四张图片的伸缩实现代码
Apr 19 Javascript
javascript实现浏览器窗口传递参数的方法
Sep 03 Javascript
Linux下使用jq友好的打印JSON技巧分享
Nov 18 Javascript
简单的jQuery入门指引
Jul 28 Javascript
Javascript实现图片轮播效果(二)图片序列节点的控制实现
Feb 17 Javascript
jQuery实现CheckBox全选、全不选功能
Jan 11 Javascript
Angular 4 依赖注入学习教程之FactoryProvider的使用(四)
Jun 04 Javascript
解决Vue不能检测数组或对象变动的问题
Feb 24 Javascript
vue 使用eventBus实现同级组件的通讯
Mar 02 Javascript
JavaScript事件委托原理与用法实例分析
Jun 07 Javascript
Vue-不允许嵌套式的渲染方法
Sep 13 Javascript
微信小程序获取用户openid的实现
Dec 24 #Javascript
vue-router启用history模式下的开发及非根目录部署方法
Dec 23 #Javascript
小程序实现人脸识别功能(百度ai)
Dec 23 #Javascript
优雅的elementUI table单元格可编辑实现方法详解
Dec 23 #Javascript
基于webpack4.X从零搭建React脚手架的方法步骤
Dec 23 #Javascript
JavaScript基于数组实现的栈与队列操作示例
Dec 22 #Javascript
jQuery实现侧边栏隐藏与显示的方法详解
Dec 22 #jQuery
You might like
PHP实时显示输出
2008/10/02 PHP
Eclipse中php插件安装及Xdebug配置的使用详解
2013/04/25 PHP
php数组操作之键名比较与差集、交集赋值的方法
2014/11/10 PHP
PHP实现随机发扑克牌
2020/04/22 PHP
Javascript和Java获取各种form表单信息的简单实例
2014/02/14 Javascript
javascript实现省市区三级联动下拉框菜单
2015/11/17 Javascript
jquery zTree异步加载、模糊搜索简单实例分享
2016/03/24 Javascript
jQuery+CSS3+Html5实现弹出层效果实例代码(附源码下载)
2016/05/16 Javascript
Google 地图事件实例讲解
2016/08/06 Javascript
fullpage.js全屏滚动插件使用实例
2016/09/06 Javascript
js实现倒计时及时间对象
2016/11/15 Javascript
解决vue里碰到 $refs 的问题的方法
2017/07/13 Javascript
微信小程序实现即时通信聊天功能的实例代码
2018/08/17 Javascript
element-ui和vue表单(对话框)验证提示语(残留)清除操作
2020/09/11 Javascript
[01:05:32]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#1COL VS Alliance第一局
2016/03/04 DOTA
[40:06]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第一场
2018/04/04 DOTA
python控制台显示时钟的示例
2014/02/24 Python
python3使用requests模块爬取页面内容的实战演练
2017/09/25 Python
Python登录并获取CSDN博客所有文章列表代码实例
2017/12/28 Python
spark: RDD与DataFrame之间的相互转换方法
2018/06/07 Python
在Python中增加和插入元素的示例
2018/11/01 Python
Python使用matplotlib 画矩形的三种方式分析
2019/10/31 Python
Numpy将二维数组添加到空数组的实现
2019/12/05 Python
tensorflow2.0的函数签名与图结构(推荐)
2020/04/28 Python
基于python生成英文版词云图代码实例
2020/05/16 Python
python如何调用php文件中的函数详解
2020/12/29 Python
CSS3中的transform属性进行2D和3D变换的基本用法
2016/05/12 HTML / CSS
中国首家奢侈品O2O网购平台:第五大道奢侈品网
2017/12/14 全球购物
Java软件工程师综合面试题笔试题
2013/09/08 面试题
《泉水》教学反思
2014/04/11 职场文书
交通违章检讨书
2014/09/21 职场文书
2015年女工委工作总结
2015/07/27 职场文书
2016教师学习教育法心得体会
2016/01/19 职场文书
golang 实现对Map进行键值自定义排序
2021/04/28 Golang
Django REST framework 限流功能的使用
2021/06/24 Python
Python实现将多张图片合成MP4视频并加入背景音乐
2022/04/28 Python