关于 XHR(XMLHttpRequest),就是 AJAX 程序。既然为 AJAX 程序,那么自然少不了对 XHR 的调用。在我之前的累积基础上,升级代码到 TypeScript。
- 《用 XHR + curl.exe 制作 ddns 客户端札记》(https://blog.csdn.net/zhangxin09/article/details/6740558)
- 《简易封装 XHR:支持 GET/POST/PUT/DELETE/JSONP/FormData》(https://blog.csdn.net/zhangxin09/article/details/78879244)
该模块源码在 https://gitee.com/sp42_admin/ajaxjs/blob/master/aj-ts/src/base/xhr.ts。
自定义 JsonParam 类型
JSON 在 js 中太常见了,我们赋予它特定的一种类型,叫 JsonParam,它的 key 只能是 string,value 可以是 string | number | boolean 任意一种。
JsonParam 定义在 aj.d.ts 中,源码如下:
/**
* JSON 实体
*/
declare type JsonParam = { [key: string]: string | number | boolean };
/**
* key 和 value 都是 string 类型的 JSON 实体
*/
declare type StringJsonParam = { [key: string]: string };
这种就是 ts 约束 JSON 结构的方法,有什么字段和类型一目了然。
当然 ts 中默认 Object 就是给 JSON 用的,但这体现不出比 js 优胜的地方,Object 十分笼统,我们还是对其类型进一步约束为好。
封装 XHR 对象
任何一个 UI 框架都会封装 XHR,我们也不例外。整个过程不复杂,只是要花点心思在如何有针对性的封装,更为清晰的可维护。
/**
* 执行请求,这是内部的函数
*
* @param url 注意 url 部分带有 # 的话则不能传参数过来
* @param cb 回调函数
* @param args 请求参数
* @param method 请求 HTTP 方法
* @param cfg 配置
*/
export function request(url: string, cb: XHR_Callback, args?: JsonParam, method: string = "GET", cfg?: XHR_Config): void {
let params: string = args ? json2url(args) : "";
let xhr: XMLHttpRequest = new XMLHttpRequest();
method = method.toUpperCase();
if (method == 'POST' || method == 'PUT')
xhr.open(method, url);
else
xhr.open(method, url + (params ? '?' + params : ''));
cb.url = url; // 保存 url 以便记录请求路径,可用于调试
// @ts-ignore
xhr.onreadystatechange = requestHandler.delegate(null, cb, cfg && cfg.parseContentType);
xhr.setRequestHeader('Accept', 'application/json');
if (method == 'POST' || method == 'PUT') {
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
} else
xhr.send(null);
}
服务端返回的都是 string 文本,进一步使用要转化为 JSON,还有 HTTP 响应的一些判断,我们写为 requestHandler()
函数。
/**
* XHR 前期执行的回调函数,进行一些初始化的工作
*
* @param this
* @param ev XHR 事件,不使用
* @param cb 回调函数
* @param parseContentType 解析响应数据的类型
*/
export function requestHandler(this: XMLHttpRequest, ev: XMLHttpRequestEventTarget, cb: XHR_Callback, parseContentType: 'text' | 'xml' | 'json'): void {
if (this.readyState === 4 && this.status === 200) {
let responseText: string = this.responseText.trim();
let data: ResponseRawData = null;
try {
if (!responseText)
throw '服务端返回空的字符串!';
switch (parseContentType) {
case 'text':
data = responseText;
break;
case 'xml':
data = this.responseXML;
break;
case 'json':
default:
try {
data = JSON.parse(responseText);
} catch (e) {
try {
data = eval("window.TEMP_VAR = " + responseText); // for {ok: true}
} catch (e) {
throw e;
}
}
}
} catch (e) {
window.alert(`XHR 错误:${e}\n访问地址是: ${cb.url}`); // 提示用户异常
}
cb(data, this);
}
if (this.readyState === 4 && this.status == 500)
window.alert('服务端 500 错误!');
}
调用 API
上面开门见山地展示了封装 XHR 过程,其实很少直接使用它们两个方法,而是使用更方便的 RESTful API,如下所示。
aj.xhr.get (url: string, cb: XHR_Callback, args?: JsonParam, cfg?: XHR_Config); // GET 请求
aj.xhr.post(url: string, cb: XHR_Callback, args?: JsonParam, cfg?: XHR_Config); // POST 表单请求
aj.xhr.put (url: string, cb: XHR_Callback, args?: JsonParam, cfg?: XHR_Config); // PUT 表单请求
aj.xhr.dele(url: string, cb: XHR_Callback, args?: JsonParam, cfg?: XHR_Config); // DLETE 请求
各参数定义如下:
/**
* XHR 回调函数
*/
type XHR_Callback = (data: any, xhr?: XMLHttpRequest) => void;
/**
* XHR 请求配置
*/
export interface XHR_Config {
/**
* HTTP 请求方法
*/
method?: string;
/**
* XHR 响应结果的类型
*/
parseContentType?: 'text' | 'xml' | 'json';
}
XHR_Callback 回调函数第一个参数为 JSON 或 HTML 文本,第二个参数为 XHR 原生对象(可选的)。在实际使用中,JSON 的情况居多,于是常常声明为 RepsonseResult
类型,如:
默认响应消息体
RepsonseResult
定义在 aj.d.ts 中,源码如下:
/**
* 后端响应的消息
*/
declare interface RepsonseResult {
/**
* 操作代码
*/
code?: number;
/**
* 操作是否成功
*/
isOk: boolean;
/**
* 操作说明
*/
msg: string;
/**
* 结果,实体,可以是任意类型
*/
result: BaseObject[];
/**
* 进行新建的时候返回的实体 id
*/
newlyId?: number
}
针对图片上传,另外设一种 ImgUploadRepsonseResult
类型。
/**
* 图片上传响应的消息
*/
declare interface ImgUploadRepsonseResult extends RepsonseResult {
/**
* 相对地址
*/
imgUrl: string;
/**
* 图片绝对地址,http 开头的
*/
fullUrl: string;
/**
* 图片列表
*/
pics: string[];
}
不得不说,这种 RepsonseResult
约束只是针对我当前后端系统的,别的可能不一样。为了不在 XHR 模块中强耦合了我的 JSON 消息体,故没有将 XHR_Callback 声明为 (data: RepsonseResult , xhr?: XMLHttpRequest) => void;
,而是 data: any
类型,更能适应不同的响应类型及其处理函数。
表单提交
对于表单,可直接使用 aj.xhr.form(formEl)
绑定表单实现 ajax 提交。该方法自动绑定表单,url 地址取决于 Form 的 action 元素,方法取决于 method 元素(这是尊重 HTML 标签的体现)。例如,
<form action="." method="${isCreate ? 'POST' : 'PUT'}">……</form>
aj.xhr.form(document.body.$('form')); // 默认的响应信息
// 或者自定义提交后的回调
aj.xhr.form(document.body.$('form'), (j: RepsonseResult) => {
if(json.isOk)
location.assign(json.newlyId);
});
// 加入提交前的拦截器
aj.xhr.form(document.body.$('form'), cb, {
beforeSubmit : (form: HTMLFormElement, json: JsonParam) => {
...
return true/fasle;// 返回 false 阻止提交表单
}
});
该过程会自动序列化表单 name/value,然后作为参数发送请求。相比 XHR_Config
,配置参数(第三个参数)增加下面配置内容。
/**
* 表单请求的配置参数
*/
export interface XhrFormConfig extends XHR_Config {
/**
* 表单中要忽略的字段
*/
ignoreField?: String;
/**
* true 表示为不进行表单验证
*/
noFormValid?: boolean;
/**
* true 表示为启用 Google 防注册机验证
*/
googleReCAPTCHA?: boolean;
/**
* 提交之前的触发的事件
*/
beforeSubmit?: (form: HTMLFormElement, json: StringJsonParam) => boolean;
}
可以忽略表单中某些的字段、支持内建的表单验证器和 Google 防注册机验证,还可以为表单里面的“返回按钮”添加后退的事件处理器。