@angular前端项目代码优化:构建Api Tree

2023-10-31

前颜(yan)

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

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

一、前后端分离(Resful api)

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

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

对应的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代码: github.com/SimpleCodeC…

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@angular前端项目代码优化:构建Api Tree 的相关文章

随机推荐

  • 预防死锁的3种方法

    1 采用资源的静态预分配策略 破坏 部分分配 条件 2 允许进程剥夺使用其他进程占用的资源破坏 不可剥夺 条件 3 采用资源有序分配法 破坏 环路 条件
  • 2022黑马SpringBoot跟学笔记(二)

    2022黑马SpringBoot跟学笔记 二 2 4 多环境配置 2 4 1 yaml文件 2 4 2 properties文件 2 4 3 命令行启动参数设置 2 4 4多环境开发控制 2 5 配置文件分类 2 5 1 代码演示 2 5
  • 谨防商家套路——一定要一样一样功能问好自己所要的功能——常年购物都被打了眼【伸手党避坑】

    本人淘宝购物十几年了 从淘宝刚出来不久就开始 自从PDD购物被骗后 觉得自己辨别商家套路能力有限 所以转战淘宝 很遗憾的是 在taobao也会有这种误导消费者的商家 常用的商家套路咱们就不说了 明眼都能看出来 但是利用惯性思维给人下套的 我
  • vue3内置组件(Teleport组件,Fragment组件)-传送组件,减少层级

    一 传送组件 Teleport组件 可将组件移动到 DOM 中 Vue app 之外的其他位置 主要是为了解决一些特殊场景下模态对话框组件 组件的渲染 例子 模拟UI框架对话框组件 App vue
  • leetcode-每日一道算法题

    字符串转换整数 atoi 请你来实现一个 myAtoi string s 函数 使其能将字符串转换成一个 32 位有符号整数 类似 C C 中的 atoi 函数 函数 myAtoi string s 的算法如下 读入字符串并丢弃无用的前导空
  • 韩信点兵问题,鸡兔同笼问题,闰年判断问题等,我用Python瞬间搞定(13)

    小朋友们好 大朋友们好 我是猫妹 一名爱上Python编程的小学生 欢迎和猫妹一起 趣味学Python 今日主题 最近猫妹一直在练习Python编程 有些习题真是经典啊 比如韩信点兵问题 比如鸡兔同笼问题等 这些问题 第一次遇到 真是没有思
  • 第0章-JDK的安装与配置

    JDK 8 的安装与配置 本系列文章参考了尚硅谷的课件文档及其他同学的学习笔记 1 安装 1 1 打开网页下载 https www oracle com 下载对应平台的合适的 JDK 版本 1 2 开始安装 双击下载的 exe 如 jdk
  • js阻止事件冒泡

    js阻止事件冒泡
  • 【ELK系列二】es的UI界面elasticsearch-head插件安装

    安装elasticsearch head插件 由于es服务启动之后 访问界面比较丑陋 为了更好的查看索引库当中的信息 我们可以通过安装elasticsearch head这个插件来实现 这个插件可以更方便快捷的看到es的管理界面 1 com
  • 爬虫学习4——Xpath爬取网页信息

    xpath是在XML文档中搜索内容的一门语言 我们常见的html是xml的一个子集 目录 安装lxml模块 获取网页数据 text 拿文本 xpath使用 1 选择同种标签的第一个 a href 根据属性href的值选择特定标签 表示后代
  • 《Docker技术入门与实战》学习笔记——第一部分 基础入门

    文章目录 0 前言 1 1 docker 镜像 1 2 docker 容器 1 3 docker 仓库 2 容器的生命周期 3 docker 命令索引 学习材料 0 前言 近期 工作中docker用的比较多 由于之前并没有怎么使用过dock
  • QT 环境搭建

    qt环境搭建 转载于 http blog csdn net gotosola article details 20397385 0 tsina 1 37407 397232819ff9a47a7b7e80a40613cfe1 Qt在Wind
  • Oracle 生成UUID

    select rawtohex sys guid from dual 文章质量建议 感谢你的创作 你可以考虑下面的改进以获得更多流量支持 增加更完备的描述 提供代码 减少外链 增加投票来和读者互动 更多建议
  • SpringBoot之bean解析(四)

    IOC思想解析 IOC 控制反转 全称为 Inverse of Control 从字面上理解就是控制反转了 将对在自身对象中的一个内置对象的控制反转 反转后不再由自己本身的对象进行控制这个内置对象的创建 而是由第三方系统去控制这个内置对象的
  • linux 配置服务开机自启动之chkconfig(Linux 运行级别)

    文章目录 linux 配置服务开机自启动 systemd 和 chkconfig add 设置开机自启动有什么区别 一 如何让一个服务或脚本开机自启动 1 三种解决方案 2 systemd 和 chkconfig 二 chkconfig 1
  • [OpenGL]射线拾取RayPicking---(2)拾取三角面

    Vries的教程是我看过的最好的可编程管线OpenGL教程 没有之一 但没有讲关于拾取 Picking 的章节 而这个功能的确很重要 就自己试着写写看了 核心代码 参考 网址如下 别问 问就是抄 https www cnblogs com
  • MATLAB自相关分析xcorr

    目录 自相关定义 xcorr的应用 举个栗子 自相关定义 信号 x t x t x t 的自相关函数定义为 其中 T为信号 x
  • 2016.5.23

    2016 5 23 news from BBC China s Science Revolution 这篇文章非常棒 推荐阅读 今天只完成4个部分 明天继续mark introduction From building the bigges
  • 将 InputStream 流转成 MultipartFile

    MultipartFile是一个接口 有一个MockMultipartFile实现类 里面有构造方法可以直接将输入流转为MutipartFile对象 MultipartFile File new MockMultipartFile file
  • @angular前端项目代码优化:构建Api Tree

    前颜 yan 在前端项目的开发过程中 往往后端会给到一份数据接口 本文简称api 为了减少后期的维护以及出错成本 我的考虑是希望能够找到这么一种方法 可以将所有的api以某种方式统一的管理起来 并且很方便的进行维护 比如当后端修改了api名