先后端集成没你想的那么难

本文首发于个人博客,转载请注明出处html

前端

网上时不时就能看到一些求助帖,大意是先后端集成所产生的一些争执,其实集成的时候若是能有一些”规范“,这件事情能够很简单。ios

技术栈

本文跟技术栈强相关,可是理论上从里面抽离出来的方法论能够实践在任意的技术栈上,只须要投入一点点时间和精力写一个小工具就行了,下面是本文所用到的一些技术栈:git

swr-request-generator

其中最后一项就是上面提到的须要本身去实现的小工具,这里我是把组里以前给redux用的工具改了一版,先是从 swagger2.0 升级到 openAPI 标准,而后改成生成 SWR 的代码而不是redux的,具体工具怎么使用能够看个人 github,里面有详细的 readme 和 example。这个工具虽然简单,倒是先后端集成不可或缺的重要组成部分,稍后咱们会讲到。程序员

SWR

而后 SWR 是一个很酷的用于获取远端数据的一个基于 React hooks 的请求库。它实现了 HTTP 规范中的 stale-while-revalidate,即其会先使用 catch 中已存在的 data 先渲染页面,而后发送新的请求去验证 catch 中的 data 是否为最新,若是已是最新则什么都不作,若是不是最新则更新本地的 catch 而后使用最新的 catch 从新渲染页面。github

这样作的好处是能够极大地提高用户体验,用户在重复浏览同一页面时,若是页面数据更新不频繁则没有任何等待时间。在新项目上使用几个月后,我欣喜地发现 SWR 登上了 ThoughtWorks 最新一期的技术雷达,和 Recoil、Svelte 一块儿暂时位于评估象限。npm

SWR in tech radar

SWR in tech radarjson

不过技术雷达也一针见血地指出了 SWR 的缺陷:redux

咱们的开发者在使用 SWR 时得到了很好的开发体验,而且由于数据老是显示在屏幕上,从而显著提高用户体验。然而,咱们提醒团队,只有当应用程序返回过期数据是合适的时候,才能使用 SWR 缓存策略。要注意,HTTP 一般要求缓存要用最新的响应返回给请求,只有在须要_很是慎重的场景_下,才会容许返回过期的响应数据。axios

也就是说,若是你的坏境对数据更新的要求极高,须要实时拿到最新的数据的话,不适合使用 SWR。

TypeScript

TypeScript 如今几乎成为了一门前端必上的技术栈,相比 JavaScript,其提供的类型系统可以保证程序员们常犯的”低级错误“在写代码时就暴露出来。

但也是因为其类型系统,咱们在集成后端 API 时须要写一堆麻烦的接口类型,好比一个 request 的参数,这个 response 的数据类型等等。

而这些接口类型实际上是后端定义的,咱们实际上是在依赖后端写的接口来写类型。这就很尴尬了,后端改了接口,前端也得跟着改类型,这多麻烦,后面咱们会一块儿解决这个问题。

为何先后端集成问题频出?

要解决一个问题,那么咱们首先应该作的是想清楚为何会出现这样的问题。

首先咱们来回忆一下常规的开发过程:拿到需求,先后端讨论出接口的各类参数,开始写代码,这中间可能后端(前端)发现有问题,而后改接口,而后差很少写好的接口得从新改,改类型,改参数。

这是常规的,不常规的呢?后端本身定好接口的参数,而后告诉你他要啥给你啥,而后你按照他给的写,写好发现不能用,去找他,他说接口改了,你从新改下。那还能咋说,只能网上对线了。

说白了就是,先后端讨论好的东西可能会变(甚至都没有通过讨论,由单方面直接决定好了),变了以后因为各类各样的缘由没能及时同步信息,即便及时同步了,改接口代码也是一件烦人的事情。

咋办?

从契约测试展开

首先,咱们须要搞清楚什么是契约测试?

契约测试,又叫”消费者驱动的契约测试“(Consumer-Driven Contracts,简称CDC)。其中有两个角色,一个消费者,一个生产者。由消费者提供一份本身的”需求清单“(契约,约定好request和response),而后生产者根据“清单”进行相应实现。以后双方依赖于这份契约进行测试和实现。

其实契约测试将依赖双方作了一个相似于解耦的操做,从消费者依赖于生产者变成双方依赖于消费者提供的契约。咱们是否是能够运用这个思想呢?

其实大多数项目里已经有一份契约了,没错,就是 swagger。只不过这份契约是生产者提供的,并且由生产者决定上面有啥,消费者无法决定。

可是其实咱们能够将这份契约作一个转换。swagger 上一般会给出 API 的详细信息,包括 request 的参数和 response 长什么样子。并且其实 swagger 只是一份 json 文件,咱们所看到的 swagger-ui 是后端的 lib 自动生成的。那么咱们是否是能够拿到这份 json 文件。解析后直接生成前端须要的 request 和类型呢?

这样带来了几个好处:

  • 前端写代码能够不用一个个写 API 了,全部的url, request params, request method, response type等等信息咱们均可以经过 swagger 自动生成
  • 后端接口改了?没问题,2秒钟更新好最新的接口代码,而后 TypeScript 的好处就体现出来了,根据新的接口类型改参数就行了
  • 生成的代码有问题?那就是后端的问题,你的实现和你本身提供的契约不一致,要么你实现不对,要么你契约不对

上代码

说了这么多都是理论,谁知道效果呢?不要紧,咱们直接上代码,下面全部的代码均可以在连接里面找到。

首先看一下这个example的目录:

. |____swagger | |____openAPI.json |____types.ts |____request | |____api.ts | |____useRequest.ts | |____client.ts |____page.tsx

其中 swagger 文件夹下是我从网上生成的 openAPI 规范的 swagger 文档。

request 文件夹下有三个文件:

  • api.ts 这个文件就是经过 @openapi-integration/swr-request-generator 根据上面的 swagger 文档生成的,里面包含 API 接口方法和相应的 request params 和 response的类型定义
  • client.js 里面只有一个单纯的 axios client instance,用于发送请求
  • useRequest 里面我将 SWR 和 axios 封装起来以供 api.ts 这个文件调用

这东西怎么用呢?

首先固然是安装这个包 npm i @openapi-integration/swr-request-generator -D

而后在项目的根目录添加一个配置文件,里面能够配置生成文件的输出目录,文件名,须要提早引入的方法,从哪里拿到 swagger 文件等(具体能够看readme)

而后跑一下 npm run ts-codegen,就会在你配置的相应生成对应的 API 文件。

好比下面这个方法就是生成的:

``import { ISWRConfig, useRequest } from "./useRequest";
import { IResponseError } from "../types";
import { client } from "./client";
export const useDownloadUsingGetRequest = (
  {
    id,
  }: {
    id: string;
  },
  SWRConfig?: ISWRConfig<IResource, IResponseError>,
) =>
  useRequest<IResource, IResponseError>(
    {
      url: /${id},
      method: "get",
    },
    SWRConfig,
  );
export interface IResource {
  description?: string;
  file?: IFile;
  filename?: string;
  inputStream?: IInputStream;
  open?: boolean;
  readable?: boolean;
  uri?: IUri;
  url?: IUrl;
}
``

而你只须要在调用的时候直接:

`const = { data, error } = useDownloadUsingGetRequest({ id: "id"})
`

假如后端改了代码,这个接口不要 id 了,要 name,你只须要从新跑一次npm run ts-codegen,而后这个方法就会更新,TypeScript 就会报错,告诉你这里要的是 name 不是 id,若是 response 的结构也改了,那么所生成的文件里面的 response 的类型也会跟着变, TypeScript 依然会提醒你 response 结构的改变。

这样一来接口不管怎么变其实前端都不在乎,顶多就是跑一遍命令,从新穿个参数就行了,大大提高开发效率,下降内耗。

相关文章
相关标签/搜索