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

栏目: JavaScript · 发布时间: 5年前

内容简介:在前端项目的开发过程中,往往后端会给到一份数据接口(本文简称api),为了减少后期的维护以及出错成本,我的考虑是希望能够找到这么一种方法,可以将所有的api以某种方式统一的管理起来,并且很方便的进行维护,比如当后端修改了api名,我可以很快的定位到该api进行修改,或者当后端添加了新的api,我可以很快的知道具体是一个api写漏了。于是,我有了构建Api Tree的想法。在前后端分离的开发模式中,前后端的交互点主要在于各个数据接口,也就是说后端把每个功能封装成了api,供前端调用。

在前端项目的开发过程中,往往后端会给到一份数据接口(本文简称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 };

复制代码

结果:

@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();
  }

}

复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ordering Disorder

Ordering Disorder

Khoi Vinh / New Riders Press / 2010-12-03 / USD 29.99

The grid has long been an invaluable tool for creating order out of chaos for designers of all kinds—from city planners to architects to typesetters and graphic artists. In recent years, web designers......一起来看看 《Ordering Disorder》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具