教你如何开发js-sdk(第二章)

大家好啊!今天又来写写写了!

今天继续上次的话题,正式说明如何从0开始开发一个js-sdk。

来个图片镇一下场子。

效果还是可以的。对于这种,首先需要进行环境准备,比如安装nodeJs,并且配置好相关的依赖仓库等等,然后新建项目,这里不多说,默认懂得都懂。不懂的也可以使用我准备好的种子项目,如果遇到安装依赖报错,请降低nodejs的版本(我电脑上安装的版本为v16.20.2),种子项目直达地址https://gitee.com/dream-sk/js-sdk-module.git。这个种子文件项目比较精简,我相信看我这篇文章的人都是有基础的,都能看得懂(虽然简单,但是也是花了很大功夫的)。里面集成了多环境配置,webpack打包,代码混淆,测试页面等功能,完成了一个基本sdk开发的框架。

下面正式开始介绍项目代码。这是本项目的文件目录,dist目录为最终打包完成的sdk文件,对于使用方来说,只需要将这个sdk放入自己项目中,使用srcipt的标签引用到项目中,即可通过sdk内部的函数进行调用。

1、面向对象抽取属性

在测试页的效果图中,我们发现这个sdk需要有两种不同的弹窗页面,这两种弹窗都有一些通用的参数。两种弹窗出了形态上不一致,其他的地方基本上一模一样。所以我们可以根据这种特性,抽取sdk的相关父类,也是上图中出现的base.js文件。

父类当中在初始化的时候,需要做好兼容,因为不是每个参数都需要调用方传过来,我们自己的sdk需要有一套自己的默认样式,调用方只需要传入一个协议编号,就可以直接打开我们的sdk,其他的参数都是可选,这样也可以防止造成用户胡乱使用导致的软件风格不一致。

2、子类对象设计

子类对象因为有两种,所以需要处理两种不同的逻辑。但是处理方法都是类似的,先构造自己的方法,导出自己需要使用的参数以及回调,在接下来的逻辑当中调用。

3、初始化

协议sdk的初始化是非常重要的,这里需要将软件的基础页面进行搭建,排布好各种样式布局,定义好各个dom元素的事件函数等等任务。

其实仔细想想,对于sdk就是需要拿到html元素中的head与body,然后往head中引入自己的css标签,往body中插入自己的dom元素,这样自然就可以被接入方使用。所以我的代码就是创建新的div元素,然后往body中append自己的元素,就可以达到目的。

4、iframe加载协议内容

之前在公司中做这个项目的时候,我也想了不少方式,正常来讲大家都是在iframe使用src属性直接引用地址,但是这种方式就少了很多交互,整个过程非常生硬,大家可以看看我是如何做的。

我是在创建iframe之前append加载动画,在进行http请求完成或失败之时,将动画移除,而不能直接使用src属性。

initBody() {
        const body = document.createElement("div");
        body.setAttribute("class", "sdk__css_draw_body");
        const iframeElement = document.createElement("iframe");
        iframeElement.setAttribute("id", "sdk__css_draw_body_iframe");
        const data = {
            agreementNo: this.agreementNo,
            version: this.version
        };
        // alert('draw:'+JSON.stringify(data));
        // 加载框
        this.loading = document.createElement("div");
        this.loading.setAttribute("class","sdk__loader");
        body.append(this.loading);
        // const contentDocument = iframeElement.contentDocument || (iframeElement.contentWindow && iframeElement.contentWindow.document);
        GET_AGREEMENT(ENV_CONFIG.REQUEST_ADDRESS, Constant.GET_AGREEMENT, data, this.timeout).then((resp) => {
            // contentDocument.open();
            // contentDocument.write(resp);
            // contentDocument.close();
            iframeElement.srcdoc = resp;
            this.loading.remove();
        }, (error) => {
            // contentDocument.open();
            // contentDocument.write(super.initErrorTips());
            // contentDocument.close();
            iframeElement.srcdoc = super.initErrorTips();
            this.loading.remove();
            typeof this.onError === "function" && this.onError();
        });
        body.append(iframeElement);
        return body;
    }

5、网络请求

对于sdk项目,根本不知道自己到底运行在什么环境中,所以不能指望接入方给你安装一些网络请求组件,我们需要使用原生js的方式,完成http请求,原生请求对象也就是XMLHttpRequest,我这里提供一个参考案例,给有需要的人。

function createXMLHttpRequest() {
    let xmlHttp;
    // 适用于大多数浏览器,以及IE7和IE更高版本
     try {
         xmlHttp = new XMLHttpRequest();
         // 前端设置是否带cookie
         xmlHttp.withCredentials = true;
     } catch (e) {
         // 适用于IE6
        try {
            xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            // 适用于IE5.5,以及IE更早版本
            try {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (e) {

            }
        }
     }
     return xmlHttp;
}

/**
 * 异步请求JSON数据
 * @param serverAddress
 * @param url
 * @param data
 * @param timeout
 * @param requestBefore
 * @param requestAfter
 * @returns {Promise<unknown>}
 */
const POST = function (serverAddress, url, data, timeout = 10000, requestBefore, requestAfter) {
    return new Promise(function (resolve, reject) {
        // 超时将强制abort,直接退出
        const timeoutHandle = setTimeout(function () {
            if (XMLHttpRequest.readyState !== 4) {
                reject(new Error('connect network timeout!'));
                XMLHttpRequest.abort();
            }
        }, timeout);

        // readyState changed
        const onReadyStateChangeHandler = function () {
            if (this.readyState !== 4) {
                return;
            }
            // 清除超时定时器
            clearTimeout(timeoutHandle);

                // 响应码200
            if (this.status === 200) {
                const respPlainText = this.response;
                resolve(respPlainText);
            } else {
                reject(new Error(this.statusText));
            }
        };
        // 创建xhr
        const XMLHttpRequest = new createXMLHttpRequest();

        // 请求前回调
        typeof requestBefore === 'function' && requestBefore();
        XMLHttpRequest.open("POST", serverAddress + url);
        XMLHttpRequest.timeout = 1000;
        XMLHttpRequest.onreadystatechange = onReadyStateChangeHandler;
        XMLHttpRequest.responseType = "json";
        XMLHttpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        const params = new URLSearchParams();
        for (let key in data) {
            if (Object.hasOwnProperty.call(data, key)) {
                if (data[key]) {
                    const value = encodeURIComponent(data[key]); // 编码值
                    params.append(encodeURIComponent(key), value); // 编码键名
                }
            }
        }
        // 获取 x-www-form-urlencoded 格式的字符串
        const formUrlEncodedString = params.toString();
        XMLHttpRequest.send(formUrlEncodedString);
        typeof requestAfter === 'function' && requestAfter();
    });
};

 由于我使用的是application/x-www-form-urlencoded,所以需要将数据转换为AAA=aaa&BBB=bbb。

6、挂载全局对象

 这里最重要的就是需要将唤起方法定义好,并且将相关的对象挂载到启动函数上,并且需要在这里进行初始化和启动,如下面代码。这里用到了代理,这个代理如果不懂,可以去看看阮一峰的《ECMAScript 6 入门教程》,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截。只要自己写一个测试demo,就可以理解了。

import Draw from "./agreement/draw";
import Modal from "./agreement/modal";

global.AgreementSDK = {};

const widgetClassMap = new Map();
widgetClassMap.set("startModal", Modal);
widgetClassMap.set("startDraw", Draw);

const functionList = ["startModal", "startDraw"];

functionList.forEach((func) => {
    const currentWidget = widgetClassMap.get(func);
    global.AgreementSDK[func] = (options) => {
        const currentProxy = new Proxy(new currentWidget(options), {
            get(target, propKey) {
                return target[propKey];
            }
        })
        currentProxy.init().then(()=> {
            currentProxy.start();
        })
    };
})

好了,到此结束,我在这里将地址发在后面,并且附上后台管理系统,请大家多多转发支持一下。

sdk:https://gitee.com/dream-sk/agreement-sdk.git

后台:https://gitee.com/dream-sk/agreementManager.git

管理站点:https://gitee.com/dream-sk/agreementWeb.git

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值