用 TypeScript 写一个轻量级的 UI 框架之五:xhr 模块

关于 XHR(XMLHttpRequest),就是 AJAX 程序。既然为 AJAX 程序,那么自然少不了对 XHR 的调用。在我之前的累积基础上,升级代码到 TypeScript。

该模块源码在 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 防注册机验证,还可以为表单里面的“返回按钮”添加后退的事件处理器。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 我行我“速” 设计师:Amelia_0503 返回首页