停止编写 API函数原因示例分析

目录
  • 正文
  • 你可能会问为什么?有一些很好的理由:
  • 一个非常简单的 CRUD 构造器
  • 高级 CRUD 构造器
    • 过滤
    • 转换和分页
    • 准备
    • 自定义接口
  • 最终的 BRUD 构造器

正文

RESTFUL API 通常提供在不同实体上执行增删改查(CRUD)操作的一组接口。我们通常在我们的前端项目中为这些每一个接口提供一个函数,这些函数的功能非常的相似,只是为了服务于不用的实体。举个例子,假设我们有这些函数。

// api/users.js
// 创建
export function createUser(userFormValues) {
  return fetch('users', { method: 'POST', body: userFormValues });
}
// 查询
export function getListOfUsers(keyword) {
  return fetch(`/users?keyword=${keyword}`);
}
export function getUser(id) {
  return fetch(`/users/${id}`);
}
// 更新
export updateUser(id, userFormValues) {
  return fetch(`/users/${is}`, { method: 'PUT', body: userFormValues });
}
// 删除
export function removeUser(id) {
  return fetch(`/users/${id}`, { method: 'DELETE' });
}

类似的功能可能存在于其他实体,例如:城市、产品、类别...但是我们可以用一个简单的函数调用来代替这些函数:

// apis/users.js
export const users = crudBuilder('/users');
// apis/cities.js
export const cities = crudBuilder('/regions/cities');

然后像这样去使用:

users.create(values);
users.show(1);
users.list('john');
users.update(values);
users.remove(1);

你可能会问为什么?有一些很好的理由:

  • 减少了代码行数:你编写的代码,和当你离开公司时其他人维护的代码
  • 强制执行 API 函数的命名约定,这可以增加代码的可读性和可维护性。例如你已经见过的函数名称: getListOfUsers, getCities, getAllProducts, productIndex, fetchCategories等, 他们都在做相同的事情,那就是“获取实体列表”。使用这种方法,你将始终拥有entityName.list()函数,并且团队中的每个人都知道这一点。

所以,让我们创建crudBuilder()函数,然后再添加一些糖。

一个非常简单的 CRUD 构造器

对于上边的简单示例,crudBuilder()函数将非常简单:

export function crudBuilder(baseRoute) {
  function list(keyword) {
    return fetch(`${baseRoute}?keyword=${keyword}`);
  }
  function show(id) {
    return fetch(`${baseRoute}/${id}`);
  }
  function create(formValues) {
    return fetch(baseRoute, { method: 'POST', body: formValues });
  }
  function update(id, formValues) {
    return fetch(`${baseRoute}/${id}`, { method: 'PUT', body: formValues });
  }
  function remove(id) {
    return fetch(`${baseRoute}/${id}`, { method: 'DELETE' });
  }
  return {
    list,
    show,
    create,
    update,
    remove
  };
}

假设约定 API 路径并且给相应实体提供一个路径前缀,他将返回该实体上调用 CRUD 操作所需的所有方法。

但老实说,我们知道现实世界的应用程序并不会那么简单。在将这种方法应用于我们的项目时,有很多事情需要考虑:

  • 过滤:列表 API 通常会提供许多过滤器参数
  • 分页:列表 API 总是分页的
  • 转换:API 返回的值在实际使用之前可能需要进行一些转换
  • 准备:formValues对象在发送给 API 之前需要做一些准备工作
  • 自定义接口:更新特定项的接口不总是${baseRoute}/${id}

因此,我们需要可以处理更多复杂场景的 CRUD 构造器。

高级 CRUD 构造器

让我们通过上述方法来构建一些日常中我们真正使用的东西。

过滤

首先,我们应该在 list输出函数中处理更加复杂的过滤。每个实体列表可能有不同的过滤器并且用户可能应用了其中的一些过滤器。因此,我们不能对应用过滤器的形状和值有任何假设,但是我们可以假设任何列表过滤都可以产生一个对象,该对象为不同的过滤器名称指定了一些值。例如,我们可以过滤一些用户:

const filters = {
  keyword: 'john',
  createdAt: new Date('2020-02-10')
};

另一方面,我们不知道这些过滤器应该如何传递给 API,但是我们可以假设(跟 API 提供方进行约定)每一个过滤器在列表 API 中都有一个相应的参数,可以以'key=value'URL 查询参数的形式被传递。

因此我们需要知道如何将应用的过滤器转换成相对应的 API 参数来创建我们的 list 函数。这可以通过将 transformFilters 参数传递给 crudBuilder() 来完成。举一个用户的例子:

function transformUserFilters(filters) {
  const params = [];
  if (filters.keyword) {
    params.push(`keyword=${filters.keyword}`);
  }
  if (filters.createdAt) {
    params.push(`create_at=${dateUtility.format(filters.createdAt)}`);
  }
  return params;
}

现在我们可以使用这个参数来创建 list 函数了。

export function crudBuilder(baseRoute, transformFilters) {
  function list(filters) {
    let params = transformFilters(filters)?.join('&');
    if (params) {
      params += '?';
    }
    return fetch(`${baseRoute}${params}`);
  }
}

转换和分页

从 API 接收的数据可能需要进行一些转换才能在我们的应用程序中使用。例如,我们可能需要将 snake_case 转换成驼峰命名或将一些日期字符串转换成用户时区。

此外,我们还需要处理分页。

我们假设来自 API 的分页数据都按照如下格式(与 API 提供者约定):

{
  data: [], // 实体对象列表
  pagination: {...} // 分页信息
}

因此,我们需要知道如何转换单个实体对象。然后我们可以遍历列表对象来转换他们。为此,我们需要一个 transformEntity 函数作为 crudBuilder 的参数。

export function crudBuilder(baseRoute, transformFilters, transformEntity, ) {
  function list(filters) {
    const params = transformFilters(filters)?.join('&');
    return fetch(`${baseRoute}?${params}`)
      .then((res) => res.json())
      .then((res) => ({
        data: res.data.map((entity) => transformEntity(entity)),
        pagination: res.pagination
      }));
  }
}

list() 函数我们就完成了。

准备

对于 createupdate 函数,我们需要将 formValues 转换成 API 需要的格式。例如,假设我们在表单中有一个 City 的城市选择对象。但是 create API 只需要 city_id。因此,我们需要一个执行以下操作的函数:

const prepareValue = formValue => ({city_id: formValues.city.id});

这个函数会根据用例返回普通对象或者 FormData,并且可以将数据传递给 API:

export function crudBuilder(baseRoute, transformFilters, transformEntity, prepareFormValues) {
  function create(formValues) {
    return fetch(baseRoute, {
      method: 'POST',
      body: prepareFormValues(formValues)
    });
  }
}

自定义接口

在一些少数情况下,对实体执行某些操作的 API 接口不遵循相同的约定。例如,我们不能使用 /users/${id} 来编辑用户,而是使用 /edit-user/${id}。对于这些情况,我们应该指定一个自定义路径。

在这里我们允许覆盖 crud builder 中使用的任何路径。注意,展示、更新、移除操作的路径可能取决于具体实体对象的信息,因此我们必须使用函数并传递实体对象来获取路径。

我们需要在对象中获取这些自定义路径,如果没有指定,就退回到默认路径。像这样:

const paths = {
  list: 'list-of-users',
  show: (userId) => `users/with/id/${userId}`,
  create: 'users/new',
  update: (user) => `users/update/${user.id}`,
  remove: (user) => `delete-user/${user.id}`
};

最终的 BRUD 构造器

这是创建 CRUD 函数的最终代码。

export function crudBuilder(baseRoute, transformFilters, transformEntity, prepareFormValues, paths) {
  function list (filters) {
    const path = paths.list || baseRoute;
    let params = transformFilters(filters)?.join('&');
    if (params) {
      params += '?';
    }
    return fetch(`${path}${params}`)
      .then((res) => res.json())
      .then(() => ({
        data: res.data.map(entity => transformEntity(entity)),
        pagination: res.pagination
      }));
  }
  function show(id) {
    const path = paths.show?.(id) || `${baseRoute}/${id}`;
    return fetch(path)
      .then((res) => res.json())
      .then((res => transformEntity(res)));
  }
  function create(formValues) {
    const path = paths.create || baseRoute;
    return fetch(path, { method: 'POST', body: prepareFormValues(formValues) });
  }
  function update(id, formValues) {
    const path = paths.update?.(id) || `${baseRoute}/${id}`;
    return fetch(path, { method: 'PUT', body: formValues });
  }
  function remove(id) {
    const path = paths.remove?.(id) || `${baseRoute}/${id}`;
    return fetch(path, { method: 'DELETE' });
  }
  return {
    list,
    show,
    create,
    update,
    remove
  }
}

Saeed Mosavat: Stop writing API functions

以上就是停止编写 API函数原因示例分析的详细内容,更多关于停止编写 API 函数的资料请关注我们其它相关文章!

(0)

相关推荐

  • javascript数组对象常用api函数小结(连接,插入,删除,反转,排序等)

    本文实例讲述了javascript数组对象常用api函数.分享给大家供大家参考,具体如下: 1. concat() 连接两个或多个数组,并返回结果 var a = [1,2,3]; var b = a.concat(6,7); console.log(a); //[1,2,3] console.log(b); //[1,2,3,6,7] 2. join(str) 把数组的所有元素用str分隔,默认逗号分隔 var a = [1,2,3] var b = a.join('|'); console.

  • javascript字符串对象常用api函数小结(连接,替换,分割,转换等)

    本文实例讲述了javascript字符串对象常用api函数.分享给大家供大家参考,具体如下: 1. concat(str1,str2,···) 连接字符串 2. indexOf(str,start) 返回 str 在字符串中首次出现的位置 var str = "hello world"; str.indexOf("hello"); // 0 str.indexOf("o",5); // 7 str.indexOf("World"

  • 详解易语言DLL以及API函数

    易语言 DLL 详细解释 使用易语言多媒体教程中的例子. .版本 2 //DLL文件 ,需要插入一个窗体.标签.按钮 .程序集 窗口程序集1 .子程序 _按钮1_被单击 窗口1.销毁 () .子程序 自创信息框, , 公开 .参数 标题, 文本型 .参数 内容, 文本型 载入 (窗口1, , 假) // 载入(),必须放在前面,放在后两句的后面则提示窗口无法载入 窗口1.标题 = 标题 窗口1.标签1.标题 = 内容 //编译为自创信息框.dll //当你想调用前面的dll时,必须先插入dll命

  • 停止编写 API函数原因示例分析

    目录 正文 你可能会问为什么?有一些很好的理由: 一个非常简单的 CRUD 构造器 高级 CRUD 构造器 过滤 转换和分页 准备 自定义接口 最终的 BRUD 构造器 正文 RESTFUL API 通常提供在不同实体上执行增删改查(CRUD)操作的一组接口.我们通常在我们的前端项目中为这些每一个接口提供一个函数,这些函数的功能非常的相似,只是为了服务于不用的实体.举个例子,假设我们有这些函数. // api/users.js // 创建 export function createUser(u

  • React Hooks钩子中API的使用示例分析

    目录 hooks是什么 Hooks的作用 使用Hooks组件前后开发模式的对比 Hooks使用策略 为什么要有Hooks useState useEffect使用 useEffect依赖项 使用情景 useMemo使用 useMemo缓存组件方式 useMemo和useEffect的区别 useCallback使用 useContext使用 useRef使用 为什么在函数组件中无法使用ref 如何在类组件中使用ref属性 自定义hooks hooks是什么 hooks理解字面意思就是钩子,是一些

  • C++ STL容器与函数谓词示例分析讲解

    目录 1.C++ vector向量 2.C++ stack 栈 3.C++ queue 队列 4.优先级队列 5.C++ list 6.c++ set 集合 7.C++ map函数 8.C++ multimap容器 9.C++ 谓词 10.C++内置预定义函数 C++ STL(Standard Template Library标准模板库),相当于java的集合模块, STL 有很多的容器. 1.C++ vector向量 (内部:封装动态大小数组作为容器,能够存放任意的动态数组) #include

  • Python调用Windows API函数编写录音机和音乐播放器

    功能描述: 1)使用tkinter设计程序界面: 2)调用Windows API函数实现录音机和音乐播放器. 参考代码: ​ 运行界面: ​ 总结 以上所述是小编给大家介绍的Python调用Windows API函数编写录音机和音乐播放器,希望对大家有所帮助!

  • Python调用Windows API函数编写录音机和音乐播放器功能

    功能描述: 1)使用tkinter设计程序界面: 2)调用Windows API函数实现录音机和音乐播放器. 参考代码: ​ 运行界面: ​ 总结 以上所述是小编给大家介绍的Python调用Windows API函数编写录音机和音乐播放器,希望对大家有所帮助!

  • 非常实用的MySQL函数全面总结详解示例分析教程

    目录 1.MySQL中关于函数的说明 2.单行函数分类 3.字符函数 4.数学函数 5.日期时间函数 6.其它常用系统函数 7.流程控制函数 8.聚合函数 1)聚合函数的功能和分类: 2)聚合函数的简单使用 3)五个聚合函数中传入的参数,所支持的数据类型有哪些? 4)聚合函数和group by的使用"最重要": 1.MySQL中关于函数的说明 "概念":类似java.python中的方法,将一组逻辑语句封装在方法体中,对外暴露方法名: "好处":

  • C++示例分析内联函数与引用变量及函数重载的使用

    目录 1.内联函数 1.1为什么使用内联函数 1.2语法 2.引用变量 2.1为什么要使用引用变量 2.2语法 2.3对于C语言的改进 3. 函数重载 3.1默认参数 3.2函数重载 1.内联函数 1.1为什么使用内联函数 减少上下文切换,加快程序运行速度. 是对C语言中的宏函数的改进. 1.2语法 #include<iostream> using namespace std; inline double square(double x){ return x*x; } int main(){

  • JavaScript高阶API数组reduce函数使用示例

    目录 正文 1.求数组中所有对象的年龄和 2.按照年龄分组 3.将数组对象转化为对象 4.参数打印 总结 正文 前面我们讲了数组的一些基本方法,今天给大家讲一下数组的reduce(),它是数组里面非常重要也是比较难的函数,那么这篇文章就好好给大家介绍下reduce函数. 还是老样子,我们直接在应用中学习,直接上例子.让我们先定义一个包含几个对象的数组,注意观察下这个数组,可以看到里面有两个对象的age都是30.(下面会用到) // 一个包含几个人物对象的数组. const people = [

  • FreeRTOS进阶列表和列表项示例分析

    目录 前言 1.初始化列表 2.初始化列表项 4.将列表项插入到列表末端 前言 FreeRTOS内核调度大量使用了列表(list)和列表项(list item)数据结构.我们如果想一探FreeRTOS背后的运行机制,首先遇到的拦路虎就是列表和列表项.对于FreeRTOS内核来说,列表就是它最基础的部分.我们在这一章集中讲解列表和列表项的结构以及操作函数,在下一章讲解任务创建时,会用到本章的知识点. 列表被FreeRTOS调度器使用,用于跟踪任务,处于就绪.挂起.延时的任务,都会被挂接到各自的列表

  • FreeRTOS进阶任务通知示例分析

    目录 在FreeRTOS版本V8.2.0中推出了全新的功能:任务通知.在大多数情况下,任务通知可以替代二进制信号量.计数信号量.事件组,可以替代长度为1的队列(可以保存一个32位整数或指针值),并且任务通知速度更快.使用的RAM更少!我在< FreeRTOS系列第14篇---FreeRTOS任务通知>一文中介绍了任务通知如何使用以及局限性,今天我们将分析任务通知的实现源码,看一下任务通知是如何做到效率与RAM消耗双赢的.        在<FreeRTOS高级篇6---FreeRTOS信

随机推荐