前言
Restful官网给出的答案可以看出,Restful具有无状态,表现力强和易于拓展的特性。今天就聊一聊Restful规范在前端的运用,我将以Typescript作为前端语言来实现 Restful API,以此来说明 Restful API 带给前端的便利。 在平时的开发过程中,首先要关注的就是前后端的接口请求;但是常常会为如何命名而苦恼;例如在用户模块中,要获取列表,我们可能要命名为 GetUserList ,但是在权限模块中要获取列表,命名就可能就是 GetAuthorityList;在一个完整的业务模块中至少得有CRUD四个方法,如果要实现一整个项目,将会有非常多的代码和文件去实现。不遵循Restful规范几乎没有办法去减少这些代码,因为每一个函数的请求路径不一致。所有我们就要提到今天的主角Restful,目的就是减少重复写请求的工作,减少因为眼花和敲错导致懵逼的情况。
封装
下面的代码就是我对业务请求的一些封装思路,遵循了Restful规范再也不用 Alt + 单机找文件方法了。
export class HttpClient {
constructor(config?: CreateAxiosDefaults) {
this.instance = axios.create(config);
}
public get<T = any>(url: string, config?: AxiosRequestConfig) {
return this.instance.get<T>(url, config);
}
public post<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
return this.instance.post<T>(url, data, config);
}
public delete<T = any>(url: string, config?: AxiosRequestConfig) {
return this.instance.delete<T>(url, config);
}
public put<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
return this.instance.put<T>(url, data, config);
}
public patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
return this.instance.patch<T>(url, data, config);
}
}
expost const httpClient = new HttpClient({...})
继承
在这个类中,我们开始对增删改查的业务请求进行一层封装;为了贴合常见的业务,实现了分页查询、单个查询、删除、修改以及新增的请求函数,分别对应到RESTFul API的 GET、DELETE、PUT、POST 方法
import { AxiosPromise, CreateAxiosDefaults } from 'axios';
import { httpClient } from '../utils';
interface Page {
page: number;
pageSize: number;
keyword?: string;
all?: boolean;
}
interface PageResult<D = any> {
page: number;
pageSize: number;
records: D[];
total: number;
}
interface BaseApiContructConfig extends Omit<CreateAxiosDefaults, ''> {
prefix: string;
}
interface BaseApiConstructOptions<D, T> {
getOne: (id: number) => AxiosPromise<D>;
getAll: (page: Page) => AxiosPromise<PageResult<D> | Pick<PageResult<D>, 'records'>>;
updateOne: (id: number, data: T) => AxiosPromise<any>;
deleteOne: (id: number) => AxiosPromise<any>;
addOne: (data: T) => AxiosPromise<any>;
}
function pathJoin(path: string) {
if (path.indexOf('/') === 0) {
path = path.substring(1);
}
return path;
}
export class BaseApi<Data = any, Param = any> implements BaseApiConstructOptions<Data, Param> {
private prefix: string;
private instance: string;
constructor(config: BaseApiContructConfig) {
this.prefix = pathJoin(config.prefix);
}
getOne = (id: number) => {
return httpClient.get(`/${this.prefix}/${id}`);
};
getAll = (page: Page) => {
return httpClient.get<PageResult<Data> | Pick<PageResult<Data>, 'records'>>(`/${this.prefix}`, { params: page });
};
deleteOne = (id: number) => {
return httpClient.delete(`/${this.prefix}/${id}`);
};
addOne = (data: Param) => {
return httpClient.post(`/${this.prefix}`, { data });
};
updateOne = (id: number, data: Param) => {
return httpClient.put(`/${this.prefix}/${id}`, { data });
};
}
到具体模块中,只需要传递这个 prefix,代表了这个请求的路径,每一个请求都遵循这个路径,如果要查找具体资源,那么在路径后面的id进行查找,无具体资源是不用关心路径的,这样来看是不是代码简单了许多,也许有人会问如果这个模块不只有CRUD的业务呢?别忘了每一个子类可以写另外的方法。
import { BaseApi } from './base';
class ArticleApi extends BaseApi {}
export const articleApi = new ArticleApi({ prefix: '/articles' });
------------------------------------------------------------------
import { BaseApi } from './base';
class ArticleApi extends BaseApi {
otherMethod = () => {
return ...
}
}
export const articleApi = new ArticleApi({ prefix: '/articles' });
希望在前端请求的封装上面提供一种不同的思路。