欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-07 08:07:10
前颜(yan) 在前端项目的开发过程中,往往后端会给到一份数据接口(本文简称api),为了减少后期的维护以及出错成本,我的考虑是希望能够找到这么一种方法,可以将所有的ap...

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

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