高级功能 最后更新时间: 2021年01月22日
为了满足UI的个性化需求,UI组件库支持开发者通过“代码”层面的手段来实现功能的改变,而不必局限在组件自身的接口上。一方面,各个组件的实现细节是公开的(比如SimpleMarker),开发者可以参照;另一方面,UI组件库支持通过继承和“模块”替换等方式对组件进行定制修改,从而实现个性化的效果。下面介绍一下UI组件库的实现原理以及如何利用这种实现来进行功能扩展。
模块管理
UI组件库的模块管理基于requirejs(AMD方式),只是封装在AMapUI这个命名空间下。
AMapUI.require
的 baseUrl
配置指向版本目录,比如1.1的版本目录是:
//webapi.amap.com/ui/1.1
模块的代码文件相对于版本目录的路径(去掉扩展名)即是模块名,比如SimpleMarker的文件地址是:
//webapi.amap.com/ui/1.1/ui/overlay/SimpleMarker.js
那么SimpleMarker模块名即是ui/overlay/SimpleMarker
模块加载
模块加载推荐使用AMapUI.load
, 该接口遵循require的语法,即:
AMapUI.load(['path/mod1','path/mod2', ....], function(mod1, mod2, ...){
//使用加载的模块
});
如果是UI模块(即路径以ui/
开头的模块),可以直接使用封装过的AMapUI.loadUI
, 同时模块名省略ui/
前缀:
//使用loadUI
AMapUI.loadUI(['overlay/SimpleMarker'], function(SimpleMarker){ ... });
//如果使用load,路径要前缀补上'ui/':
AMapUI.load(['ui/overlay/SimpleMarker'], function(SimpleMarker){ ... });
引入多个模块,模块名数组传入多个路径即可,回调函数的参数与路径顺序保持一致:
AMapUI.load(['lib/utils', 'ui/overlay/SimpleMarker'], function(utils, SimpleMarker) {
//调用utils, SimpleMarker
});
预加载
AMapUI.loadUI
是一种动态加载的方式,在代码执行时才会加载对应的UI资源;如果您对加载速度较敏感,可以提前引入某个UI模块的script文件,从而加快执行速度。比如:
1.0版本SimpleMarker的模块文件是: //webapi.amap.com/ui/1.1/ui/overlay/SimpleMarker.js
,可以在UI组件库的入口文件后提前引入:
<script src="//webapi.amap.com/ui/1.1/main.js"></script>
<!--预加载 SimpleMarker -->
<script src="//webapi.amap.com/ui/1.1/ui/overlay/SimpleMarker.js"></script>
<script type="text/javascript">
//SimpleMarker已经加载,这里loadUI可以立即回调
AMapUI.loadUI(['overlay/SimpleMarker'].....
</script>
复用requirejs
如果您的应用同样依赖 requirejs 做模块管理,可以直接使用UI组件库内部的requirejs,无需另行引入。
//设置自己的require
window.require = AMapUI.requirejs.config({
context: "my_context", // 建议设置context,参见 http://requirejs.org/docs/api.html#multiversion
baseUrl: "...my_mods_path..." // 模块的部署路径
//...其他requirejs设置
});
//加载自己的mod模块(相对于上述设置的baseUrl)
require(['mod'],function(mod){
//
});
引用内部模块
UI组件库的实现细节是公开的,如果某些内部模块对您的开发有帮助,您同样可以通过AMapUI.load
引入这些模块,比如引入UI组件库内部使用的lib/utils
:
AMapUI.load(['lib/utils'], function(utils) {
//调用utils的相关方法....
});
不过请注意,尽管可以预期被大量使用的模块不会被轻易改动,但版本变动(需要您手动更新UI组件库的入口文件地址)确实会带来兼容性风险,需要您谨慎对待。
常用的内部模块有:
- lib/$, 即DomLibrary(jQuery或者Zepto)
- lib/underscore-tpl,underscore语法的模板引擎,包括下列三种语法:
- <%- foo %>,html编码后输出
- <%= foo %>,原值输出
- <% ..code.. %>,js代码,比如if, for等
- lib/utils,utils下包含一些常用的工具方法,比如extend,inherit等
继承
通常UI以构造函数类的方式提供,比如SimpleMarker,如果您需要扩展SimpleMarker的能力,可以通过如下的继承方式实现:
//使用load接口引入utils以及目标UI
AMapUI.load(['lib/utils', 'ui/overlay/SimpleMarker'], function(utils, SimpleMarker) {
//新的Marker类
function MyMarker(opts) {
//调用父级的构造方法
MyMarker.__super__.constructor.call(this, opts);
//..额外的初始化逻辑..
}
//继承SimpleMarker的功能
utils.inherit(MyMarker, SimpleMarker);
//增加或者覆盖接口
utils.extend(MyMarker.prototype, {
//..新的接口
});
});
模块“覆盖”
假设某个UI的界面仅需要微调一下(比如互换两个节点的位置),就能满足您的需求,而该UI自身并没有提供这样的修改接口,UI组件库允许您从代码层面直接“覆盖”掉原来的模块实现。
从requirejs的角度,如果想覆盖模块A的实现, 只要在A模块被引用之前,调用define重新定义即可:
AMapUI.define('模块名',[新的依赖],function(){
//返回新的实现
});
考虑到代码的执行顺序和书写的方面性,UI组件库允许您提前定义好某个模块的实现,后续加载时,一旦检测到某个模块名已经被定义过,则不再重新定义,从而达到了“覆盖”默认模块实现的目的(更准确的说法是优先使用您的前置定义)
具体而言,UI组件内部普遍采用AMapUI.weakDefine
来定义依赖的模块,与原始define
的区别是: weakDefine
不会覆盖已经存在的模块定义(因此比define
要弱)。 如果某个模块在加载之前已经被AMapUI.define
过,那么该UI执行的过程中就会使用这个前置定义好的模块实现。
以1.0版本SimpleInfoWindow为例,其代码路径是:
//webapi.amap.com/ui/1.1/ui/overlay/SimpleInfoWindow.js
其中存在如下的dom结构定义:
//注意,这里是weakDefine
AMapUI.weakDefine("polyfill/require/require-text/text!ui/overlay/SimpleInfoWindow/tpl/container.html", [], function() {
// return ....html代表的dom结构...
});
复制该段定义,AMapUI.weakDefine
替换为AMapUI.define
,第一个参数(模块名)和第二参数(依赖)通常都不需要改动,重新实现第三个function参数(返回新的模块定义):
//完整复制模块名,重新定义该模块的实现,注意,这里是AMapUI.define
AMapUI.define("polyfill/require/require-text/text!ui/overlay/SimpleInfoWindow/tpl/container.html", [], function() {
//return 新的html结构
});
//加载执行
AMapUI.loadUI(['overlay/SimpleInfoWindow'], function(SimpleInfoWindow) {
//SimpleInfoWindow此时使用的是新定义的dom结构
});
调试模式
为减少请求次数,各UI的代码经过打包处理(即将依赖的模块合并到同一个文件中)。打包后的文件依然有很好的可读性,比如 SimpleMarker。 如果您有兴趣查看UI内部各个依赖模块的原始实现,可以进入“调试”模式,调试模式下UI依赖的模块将逐一单独加载(可以通过浏览器调试工具中的网络请求观察到这一点)。
具体方法是:在当前页面地址上附加get参数debugAMapUI=1
(比如这个地址 )。网络请求的变化见如下截图: