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