查看: 3232|回复: 0

[HTML代码] 【quickhybrid】API多平台支撑的实现

发表于 2018-1-2 08:00:00
前言

在框架规划时,就有提到过这个框架的一些常用功能需要支持H5环境下的调用,也就是需要实现API的多平台支撑

为什么要多平台支撑?核心仍然是复用代码,比如在微信下,在钉钉下,在quick容器下,
如果没有多平台支撑,那么quick.ui.alert只能用于quick容器下,钉钉和微信下就得分别用其它代码实现,
代码复用率低,如果实现了多平台支撑。那么三个平台中同一个功能的代码则是一样的。

什么样的多平台支撑

当然了,本框架实现的多平台支撑和一般框架的有点区别。

一般的框架中支持多个平台更多的是一个polyfill,譬如

  1. // 假设以前不支持h5
  2. const oldToast = quick.ui.toast;
  3. quick.ui.toast = function(...) {
  4. if (os.h5) {
  5. // 做一些h5中做的
  6. ...
  7. } else {
  8. oldToast(...);
  9. }
  10. };
复制代码

这就是垫片实现,如果是新的环境,用新的实现,否则用老的实现

而__本框架中的多平台实现是直接内置到了框架核心中__,也就是说框架本身就支持多平台API的设置

  1. quick.extendModule('ui', [{
  2. namespace: 'toast',
  3. os: ['h5'],
  4. defaultParams: {
  5. message: '',
  6. },
  7. runCode(...rest) {
  8. // 定义h5环境中的做法
  9. ...
  10. },
  11. }, ...];
  12. quick.extendModule('ui', [{
  13. namespace: 'toast',
  14. os: ['quick'],
  15. defaultParams: {
  16. message: '',
  17. },
  18. runCode(...rest) {
  19. // 定义quick环境中的做法
  20. ...
  21. },
  22. }, ...];
复制代码

在框架内部定义API时,不再是直接的quick.ui.alert = xxx,而是通过特定的API单独给某个环境下定义实现

而且,框架中的定义,每一个API都是有quick,h5环境下的实现的。

多平台支撑的核心

从上述的介绍中也可以看到,多平台支撑主要是前端的实现,与原生API,原生API在这里面只能算一个环境下的实现

核心就是基于:Object.defineProperty,重写set和get

  1. Object.defineProperty(apiParent, apiName, {
  2. configurable: true,
  3. enumerable: true,
  4. get: function proxyGetter() {
  5. // 需要根据不同的环境,返回对应下的内容
  6. ...
  7. },
  8. set: function proxySetter() {
  9. // 可以提示禁止修改API
  10. },
  11. });
复制代码

本框架中的多平台实现代码可以参考源码,这里不赘述,下文中会介绍如何简单的实现一个多平台支撑API

实现一个多平台支撑API

我们先预设最终的结果:

  1. quick.os.quick = true;
  2. quick.ui.alert('hello'); // quick-hello
  3. quick.os.quick = false;
  4. quick.ui.alert('hello'); // h5-hello
  5. quick.ui.alert = 11; // 提示:不允许修改quick API
复制代码

那么要达到上述的要求,应该如何做呢?

写一个雏形

最简单的,先假设这些实现都已经存在,然后直接基于defineProperty返回

  1. function alertH5(message) {
  2. alert('h5-' + message);
  3. }
  4. function alertQuick(message) {
  5. alert('quick-' + message);
  6. }
  7. const quick = {};
  8. quick.ui = {};
  9. quick.os = {
  10. quick: false,
  11. };
  12. Object.defineProperty(quick.ui, 'alert', {
  13. configurable: true,
  14. enumerable: true,
  15. get: function proxyGetter() {
  16. // 需要根据不同的环境,返回对应下的内容
  17. if (quick.os.quick) {
  18. return alertQuick;
  19. } else {
  20. return alertH5;
  21. }
  22. },
  23. set: function proxySetter() {
  24. // 可以提示禁止修改API
  25. alert('不允许修改quick API');
  26. },
  27. });
复制代码

那么,它的调用结果是

  1. quick.os.quick = true;
  2. quick.ui.alert('hello'); // quick-hello
  3. quick.os.quick = false;
  4. quick.ui.alert('hello'); // h5-hello
  5. quick.ui.alert = 11; // 提示:不允许修改quick API
复制代码

虽然效果和预设的一样,但是很明显还需优化完善

增加拓展API的方法

拓展方式的定义如下

  1. const quick = {};
  2. quick.os = {
  3. quick: false,
  4. };
  5. /**
  6. * 存放所有的代理 api对象
  7. * 每一个命名空间下的每一个os都可以执行
  8. * proxyapi[namespace][os]
  9. */
  10. const proxysApis = {};
  11. // 支持的所有环境
  12. const supportOsArray = ['quick', 'h5'];
  13. function getCurrProxyApiOs(currOs) {
  14. for (let i = 0, len = supportOsArray.length; i < len; i += 1) {
  15. if (currOs[supportOsArray[i]]) {
  16. return supportOsArray[i];
  17. }
  18. }
  19. // 默认是h5
  20. return 'h5';
  21. }
  22. // 如获取quick.ui.alert
  23. function getModuleApiParentByNameSpace(module, namespace) {
  24. let apiParent = module;
  25. // 只取命名空间的父级,如果仅仅是xxx,是没有父级的
  26. const parentNamespaceArray = /[.]/.test(namespace) ? namespace.replace(/[.][^.]+$/, '').split('.') : [];
  27. parentNamespaceArray.forEach((item) = >{
  28. apiParent[item] = apiParent[item] || {};
  29. apiParent = apiParent[item];
  30. });
  31. return apiParent;
  32. }
  33. function proxyApiNamespace(apiParent, apiName, finalNameSpace) {
  34. // 代理API,将apiParent里的apiName代理到Proxy执行
  35. Object.defineProperty(apiParent, apiName, {
  36. configurable: true,
  37. enumerable: true,
  38. get: function proxyGetter() {
  39. // 确保get得到的函数一定是能执行的
  40. const nameSpaceApi = proxysApis[finalNameSpace];
  41. // 得到当前是哪一个环境,获得对应环境下的代理对象
  42. return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
  43. },
  44. set: function proxySetter() {
  45. alert('不允许修改quick API');
  46. },
  47. });
  48. }
  49. function extendApi(moduleName, apiParam) {
  50. if (!apiParam || !apiParam.namespace) {
  51. return;
  52. }
  53. if (!quick[moduleName]) {
  54. quick[moduleName] = {};
  55. }
  56. const api = apiParam;
  57. const modlue = quick[moduleName];
  58. const apiNamespace = api.namespace;
  59. const apiParent = getModuleApiParentByNameSpace(modlue, apiNamespace);
  60. // 最终的命名空间是包含模块的
  61. const finalNameSpace = moduleName + '.' + apiNamespace;
  62. // 如果仅仅是xxx,直接取xxx,如果aa.bb,取bb
  63. const apiName = /[.]/.test(apiNamespace) ? api.namespace.match(/[.][^.]+$/)[0].substr(1) : apiNamespace;
  64. // 这里防止触发代理,就不用apiParent[apiName]了,而是用proxysApis[finalNameSpace]
  65. if (!proxysApis[finalNameSpace]) {
  66. // 如果还没有代理这个API的命名空间,代理之,只需要设置一次代理即可
  67. proxyApiNamespace(apiParent, apiName, finalNameSpace);
  68. }
  69. // 一个新的API代理,会替换以前API命名空间中对应的内容
  70. const apiRuncode = api.runCode;
  71. const oldProxyNamespace = proxysApis[finalNameSpace] || {};
  72. proxysApis[finalNameSpace] = {};
  73. supportOsArray.forEach((osTmp) = >{
  74. if (api.os && api.os.indexOf(osTmp) !== -1) {
  75. // 如果存在这个os,并且合法,重新定义
  76. proxysApis[finalNameSpace][osTmp] = apiRuncode;
  77. } else if (oldProxyNamespace[osTmp]) {
  78. // 否则仍然使用老版本的代理
  79. proxysApis[finalNameSpace][osTmp] = oldProxyNamespace[osTmp];
  80. }
  81. });
  82. }
  83. function extendModule(moduleName, apis) {
  84. if (!apis || !Array.isArray(apis)) {
  85. return;
  86. }
  87. if (!quick[moduleName]) {
  88. quick[moduleName] = [];
  89. }
  90. for (let i = 0, len = apis.length; i < len; i += 1) {
  91. extendApi(moduleName, apis[i]);
  92. }
  93. }
  94. quick.extendModule = extendModule;
复制代码

上述代码中增加了些复杂度,有一个统一管理所有代理调用的池,然后每次会更新对于环境下的代理

基于上述的方式可以如下拓展对于环境下的API

  1. quick.extendModule('ui', [{
  2. namespace: 'alert',
  3. os: ['h5'],
  4. defaultParams: {
  5. message: '',
  6. },
  7. runCode(message) {
  8. alert('h5-' + message);
  9. },
  10. }]);
  11. quick.extendModule('ui', [{
  12. namespace: 'alert',
  13. os: ['quick'],
  14. defaultParams: {
  15. message: '',
  16. },
  17. runCode(message) {
  18. alert('quick-' + message);
  19. },
  20. }]);
复制代码

最终的调用如下(结果和预期一致)

  1. quick.os.quick = true;
  2. quick.ui.alert('hello'); // quick-hello
  3. quick.os.quick = false;
  4. quick.ui.alert('hello'); // h5-hello
  5. quick.ui.alert = 11; // 提示:不允许修改quick API
复制代码

虽然就一两个API来说,这类拓展方式看起来很复杂,但是当API一多,特别是还需批量预处理时(如默认参数,Promise支持等),它的优势就出来了

多平台支撑在quick中的应用

quick hybrid框架中,默认支持quick和h5有种环境,核心代码就是上述列举的(当然,内部增加了一些代理,默认参数处理等,会稍微复杂一点)。

基于这个核心,然后可以将框架的定义和API定义分开打包

  1. quick.js
  2. quick.h5.js
复制代码

这样,最终看起来h5下的API定义就是一个拓展包,是没有它也不会影响quick环境下的使用,而且,如果增加一个新的环境(比如dd),
只需要再新增另一个环境的拓展包而已,各种写法都是一样的,这样便于了统一维护

返回根目录
  • 【quickhybrid】如何实现一个Hybrid框架
源码

github上这个框架的实现

quickhybrid/quickhybrid




上一篇:vue-router实例
下一篇:Angular作用域
回复

使用道具 举报