定義標準接口(interface),各端模塊各自獨立實現(xiàn),編譯時和運行時對實現(xiàn)的接口輸入輸出做檢查。
主要 2 個目標:
CML 的是多端的上層應用語言,在這樣的目標下,用戶擴展功能時,保障業(yè)務代碼和各端通信一致性變得特別重要。
用戶也許只實現(xiàn)一個 API 跨 2 端,保障一致很簡單,在一個超過 5 萬行代碼的復雜應用里,用戶擴展了 100 個接口呢,如果你覺得還很簡單,那跨 6 個端呢,在應用持續(xù)高速迭代中讓用戶人肉保障多端一致性實在太艱難,即使能做到,可維護性也會極差,跨端也會失去意義。
以上,跨端很美好,最大風險是可維護性問題。多態(tài)協(xié)議是 CML 業(yè)務層代碼和各端底層組件和接口的分界點,CML 會嚴格“管制”輸入輸出值的類型和結(jié)構,同時會嚴格檢查業(yè)務層 JS 代碼,避免直接使用某端特有的接口,不允許在公共代碼處使用某個端特定的方法,即使這段代碼不會執(zhí)行,例如禁止使用window、wx、my、swan、 weex 等方法。
統(tǒng)一多態(tài)協(xié)議設計的靈感來自于Apache Thrift - 可伸縮的跨語言服務開發(fā)框架,本質(zhì)上跨端也屬于跨語言。 它能讓 CML 開發(fā)者快速接入各個客戶端底層功能,且不會因為各端接口差異、產(chǎn)品需求差異導致正常業(yè)務代碼被打散,變得可讀性差、難以維護,避免結(jié)果適得其反,具體 Case;各個客戶端底層功能實現(xiàn)可以一部分來自 CML 提供的基礎組件和基礎 api 庫,一部分來自 CML 開發(fā)者,一部分來自各端生態(tài)開源庫(Chameleon 擁抱開源社區(qū),你可以直接安裝某個端的組件在使用多態(tài)協(xié)議擴展到某個端使用)。
項目根目錄下執(zhí)行cml init component,選擇Polymorphic function,輸入文件名稱,例如utils,生成如下文件結(jié)構
├── components
└──utils
└── utils.interface
初始化文件內(nèi)容如下:
<script cml-type="interface">
interface UtilsInterface {
getMsg(msg: string): void;
}
</script>
<script cml-type="web">
class Method implements UtilsInterface {
getMsg(msg) {
return 'web:' + msg;
}
}
export default new Method();
</script>
<script cml-type="weex">
class Method implements UtilsInterface {
getMsg(msg) {
return 'weex:' + msg;
}
}
export default new Method();
</script>
<script cml-type="wx">
class Method implements UtilsInterface {
getMsg(msg) {
return 'wx:' + msg;
}
}
export default new Method();
</script>
<script cml-type="alipay">
class Method implements UtilsInterface {
getMsg(msg) {
return 'alipay:' + msg;
}
}
export default new Method();
</script>
<script cml-type="baidu">
class Method implements UtilsInterface {
getMsg(msg) {
return 'baidu:' + msg;
}
}
export default new Method();
</script>
文件中利用標簽將各端代碼進行物理隔離,利用 cml-type 屬性指定平臺。 cml-type="interface"為接口定義部分,利用接口校驗語法定義這個接口的方法及方法的參數(shù)與返回值。
cml-type="web|wx|weex|alipay|baidu"為各端實現(xiàn)部分,按照 interface 接口的定義進行方法的實現(xiàn)輸入輸出。注意要以export default的形式導出對象。
在需要使用多態(tài)接口的組件中引入,例如src/pages/index/index.cml中引用,代碼如下:
import utils from '../../components/utils/utils.interface';
let message = utils.getMsg();
多態(tài)接口適用于因為端的不同而進行不同接口的調(diào)用或者不同業(yè)務邏輯處理的場景。 例如:我們的頁面現(xiàn)在需要一個本地存儲功能的需求,我們已知各端的接口調(diào)用方法
如果不使用多態(tài)接口我們只能根據(jù)不同環(huán)境去調(diào)用各自的接口
if (process.env.platform === 'web') {
localStorage.setItem(key, value, function(e) {});
} else if (process.env.platform === 'wx') {
wx.setStorageSync(key, value);
} else if (process.env.platform === 'alipay') {
my.setStorageSync(key, value);
} else if (process.env.platform === 'baidu') {
swan.setStorageSync(key, value);
} else if (process.env.platform === 'weex') {
storage.setItem(key, value, function(e) {});
}
這樣的代碼有如下 待解決問題:
利用了多態(tài)接口之后的使用方式如下:
import utils from 'utils.interface';
utils.setStorage(key, value, cb);
utils.interface對 setStorage 進行了封裝,文件內(nèi)容如下:
<script cml-type="interface">
// 定義一個傳參為string類型,返回值為undefine的函數(shù)類型
type Callback = (state: string) => undefined;
// 定義模塊的interface
interface UtilsInterface {
// 定義setStorage方法 參數(shù)個數(shù)及返回值類型
setStorage(key: string, value: string, cb: Callback): undefined;
}
</script>
<script cml-type="web">
// web端接口實現(xiàn)
class Method implements UtilsInterface {
setStorage(key, value, cb) {
try {
localStorage.setItem(key, value);
cb('success');
}
cache(e) {
cb('fail');
}
}
}
export default new Method();
</script>
<script cml-type="weex">
// weex端接口實現(xiàn)
class Method implements UtilsInterface {
setStorage(key, value, cb) {
storage.setItem(key, value,
function(e) {
if (e.result == "success") {
cb('success');
} else {
cb('fail');
}
});
}
}
export default new Method();
</script>
<script cml-type="wx">
// wx端接口實現(xiàn)
class Method implements UtilsInterface {
setStorage(key, value, cb) {
try {
wx.setStorageSync(key, value);
cb('success');
}
catch(e) {
cb('fail');
}
}
}
export default new Method();
</script>
<script cml-type="alipay">
// alipay端接口實現(xiàn)
class Method implements UtilsInterface {
setStorage(key, value, cb) {
try {
my.setStorageSync(key, value);
cb('success');
}
catch(e) {
cb('fail');
}
}
}
export default new Method();
</script>
<script cml-type="baidu">
// baidu端接口實現(xiàn)
class Method implements UtilsInterface {
setStorage(key, value, cb) {
try {
swan.setStorageSync(key, value);
cb('success');
}
catch(e) {
cb('fail');
}
}
}
export default new Method();
</script>
CML 在跨端的統(tǒng)一性上做了很多的工作,但即使是做到了 99%的統(tǒng)一,仍然存在著 1%的差異,基于代碼可維護性的考量,CML 引入了多態(tài)協(xié)議。
項目根目錄下執(zhí)行cml init component,選擇Polymorphic component,輸入組件名稱,例如c-list,生成如下文件結(jié)構
├── components
│ ├── c-list
│ │ ├── c-list.interface
│ │ ├── c-list.web.cml
│ │ ├── c-list.weex.cml
│ │ ├── c-list.wx.cml
│ │ ├── c-list.alipay.cml
│ │ ├── c-list.baidu.cml
│ │ └── ...
.interface文件利用接口校驗語法對組件的屬性和事件進行類型定義, 保證各端的組件和事件一致,框架在開發(fā)環(huán)境的運行時做校驗。例如c-list.interface
type eventType = 'change';
type eventDetail = {
value: string
}
type changeEvent = (a: eventType, detail: eventDetail) => void;
export default Interface Clist {
name: string,
age: number,
changeEvent: changeEvent
}
c-list.web.cml、c-list.weex.cml、c-list.wx.cml、c-list.alipay.cml、c-list.baidu.cml、... 文件是灰度區(qū),它是唯一可以調(diào)用下層端組件的 CML 文件,分別是 web、weex、wx、alipay、baidu 五個端的調(diào)用入口。建議這一塊代碼盡量薄,只是用來調(diào)用下層端代碼,不要編寫過于重的代碼。
CML 中的組件是采用單文件格式的 cml 文件,其中包括了一個組件所擁有的視圖層、邏輯層及配置信息??紤]以下兩種場景:
以場景一為例,先看一個最容易理解的跨端組件實現(xiàn):
<template c-if="{{ENV === 'web'}}">
<ul c-for="{{list}}">
<li>{{ item.name }}</li>
</ul>
</template>
<template c-else-if="{{ENV === 'wx'}}">
// 假設wx-list 是微信小程序原生的組件
<wx-list data="{{list}}"></wx-list>
</template>
<template c-else-if="{{ENV === 'alipay'}}">
// 假設alipay-list 是微信小程序原生的組件
<alipay-list data="{{list}}"></alipay-list>
</template>
<template c-else-if="{{ENV === 'baidu'}}">
// 假設baidu-list 是微信小程序原生的組件
<baidu-list data="{{list}}"></baidu-list>
</template>
<template c-else-if="{{ENV === 'weex'}}">
// 假設list 是weex端原生的組件
<list source="{{list}}"></list>
</template>
上面的代碼塊是一個簡單的列表實現(xiàn),wx 和 weex 都是使用了各自的原生組件,這樣的實現(xiàn)方法其實是把三端或者 N 端的模版放在了同一個文件中,當然,這里只是展示了模版的復雜,假設在 js 代碼塊中也存在著端的判斷,那代碼的復雜可想而知。
總結(jié)下來,這樣的代碼有如下 待解決問題:
而利用了多態(tài)組件之后的使用方式如下:
<c-list data="{{list}}"><c-list></c-list></c-list>
可以看到我們只引用了一個c-list組件,該組件提供了統(tǒng)一的屬性。
chameleon-tool@1.0.4-alpha.2 開始支持
<template class="demo-com">
<cml type="weex">
<view>weex端代碼以這段代碼進行渲染</view>
<demo-com title="我是標題weex"></demo-com>
</cml>
<cml type="alipay,baidu">
<view>alipay和baidu端以這段代碼進行渲染</view>
<demo-com title="我是標題2"></demo-com>
</cml>
<cml type="wx">
<view>wx端以這段代碼進行渲染</view>
<demo-com title="我是標題wx"></demo-com>
</cml>
<cml type="base">
<view
>如果找不到對應端的代碼,則以type='base'這段代碼進行渲染,比如這段代碼會在web端進行渲染</view
>
<demo-com title="我是標題base"></demo-com>
</cml>
</template>
1.必須符合如上結(jié)構,template 標簽下第一層并列標簽必須是 cml 標簽;
2.cml 標簽是必須有 type 屬性,表明應用于哪端,cml 標簽最終會被替換為 view 標簽
更多建議: