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

前颜(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 };

结果:

优化后的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

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

(0)

相关推荐

  • 利用Angular2的Observables实现交互控制的方法

    在Angular1.x中,我们使用Promise来处理各种异步.但是在angular2中,使用的是Reactive Extensions (Rx)的Observable.对于Promise和Observable的区别,网上有很多文章,推荐egghead.io上的这个7分钟的视频(作者 Ben Lesh).在这个视频的介绍中,主要说的,使用Observable创建的异步任务,可以被处理,而且是延时加载的.这篇文章里,我们主要针对一些在跟服务器端交互的时候遇到的问题,来看看Observable给我们

  • angular中两种表单的区别(响应式和模板驱动表单)

    angular的表单 angular的表单分为两种,一种是响应式的表单,另一种是模板驱动表单.使用'@angular/forms'库中的FormGroup, FormControl,FormArray,FormBuilder 等类构建出的数据对象就是响应式的表单,在响应式的表单中,我们会在数据源里面进行各种操作,像添加校验等,在html文件中使用 formGroup,formGroupName,formControlName等将数据和视图进行绑定(需要引入ReactiveFormsModule)

  • 浅谈Angular7 项目开发总结

    由于公司需要,开始学习angular,这个传闻中学习曲线及其陡峭的前端框架,并开始写第一个用angular的项目,截止今天初步完成现有需求,顾在此做一次遇到问题的总结,以便知识的掌握. 一.在angular项目中,如何使用锚点 在常规项目中,使用锚点用来做"智能"定位效果时,只需这么写: <a href="#test" rel="external nofollow" >走你</a> <div id="tes

  • Angular(5.2->6.1)升级小结

    在前面的文章中也曾经分别提到过,angular6由于存在一些稍大的变化,所以不能像Angular4到Angular5那样基本无感地进行升级,这里结合官方提示,简单整理一下Angular5.2到目前稳定的6.1的升级要点. 事前准备 变更内容 除此之外,还需要确认如下内容: tsconfig.json: preserveWhitespaces设定为off(v6缺省设定) package.json中scripts的使用,所有的cli命令统一使用两个横线–传入参数(POSIX规范) ngModelCh

  • Angular5升级RxJS到5.5.3报错:EmptyError: no elements in sequence的解决方法

    前言 RxJS是一种针对异步数据流编程工具,或者叫响应式扩展编程:可不管如何解释RxJS其目标就是异步编程,Angular引入RxJS为了就是让异步可控.更简单.可是最近在升级中遇到了一些问题,下面就来给大家介绍下,给同样遇到这个问题发朋友们一些参考,下面话不多说了,来一起看看详细的介绍吧. Angular 5.0.5升级RxJS到5.5.3报错: ERROR Error: Uncaught (in promise): EmptyError: no elements in sequence Em

  • angular 用Observable实现异步调用的方法

    Observable(可观察对象) Observable(可观察对象)是基于推送(Push)运行时执行(lazy)的多值集合. 拉取(Pull)和推送(Push) 拉取和推送是数据生产者和数据消费者之间通信的两种不同机制. 拉取:在拉取系统中,总是由消费者决定何时从生产者那里获得数据.生产者对数据传递给消费者的时间毫无感知(被动的生产者,主动的消费者) 推送:在推送系统中生产者决定何时向消费者传递数据,消费者对何时收到数据毫无感知(被动的消费者) js中的Promise和Observable 现

  • Angular2平滑升级到Angular4的步骤详解

    前言 Angular4终于在两天前发布了正式版本,那么怎么升级呢?其实Angular2和Angular4之间属于平滑过渡,并不像1和2之间颠覆性的重写代码. Angular4现已发布  http://www.jb51.net/article/109685.htm 为什么跳过Angular 3? 根据Angular团队首席开发Igor Minar的说法:随着Angular 2的发布,Angular团队引入了语义化版本控制规范,即:将语义化版本用三组数字来表示,按照major.minor.patch

  • angular6 填坑之sdk的方法

    技术背景:angular + ant zorro 最为大型前端团队首选的前端技术框架,angular,在国内多少还是有些水土不服.本人将针对angular做个一系列的填坑分享. 坑一:sdk angular的sdk不属于各个模块,直接挂载在body下面, ant design直接使用sdk,导致任何的弹出层,如select,dropdown,picker等在弹出来的时候自动创建覆盖全局的sdk,需要点击sdk才能关闭已打开的下拉. 明显需要点击两次才能出现一个下拉是产品们不能接受的. 解决方案有

  • Angular项目如何升级至Angular6步骤全纪录

    前言 前段时间将所负责的 Angular2 项目升级到了 Angular5 版本,这两天又进行了升级至 Angular6 的尝试.总的来说,两次升级过程比较类似,也不算复杂. 2018年5月4日,Angular6.0.0版正式发布,新版本主要关注底层框架和工具链,目的在于使其变得更小更快. 特性的小改动: animations: 只能使用 WA-polyfill 和 AnimationBuilder animations: 在转换匹配器中暴露元素和参数 common: 在 NgIf 中使用非模板

  • 详解从angular-cli:1.0.0-beta.28.3升级到@angular/cli:1.0.0

    现在Angular CLI在npm下通过@angular/cli来替代angular-cli,并且它只支持Node6.9.0或更高的版本,npm 3或更高的版本.所以在升级angular cli之前,请先升级node和npm. 如果你使用Angular CLI beta.28或更低的版本,你需要先卸载掉 angular-cli包: npm uninstall -g angular-cli npm uninstall --save-dev angular-cli 如果你忘记了更新npm,执行上面第

随机推荐