BUI 是用來快速構建界面交互的漸進式UI框架, 專一webapp開發, 開發者只需關注業務的開發, 界面的佈局及交互交給BUI, 開發出來的應用, 能夠嵌入平臺 ( 微信公衆號, 微信小程序webview, 聆客, 釘釘, 淘寶, 支付寶等 ), 亦能夠跟其它第三方平臺打包成獨立應用( Bingotouch , Cordova , Dcloud , APICloud , Appcan 等), 最終能夠全跨平臺展現. (包括Ipad)結合BUI提供的BUI-Fast編輯插件, NPM工具, BUI更是一個移動快速開發的解決方案. 能夠解決如下常見問題.php
什麼是組件化呢?
組件化是指解耦複雜系統時將多個功能模塊拆分、重組的過程,有多種屬性、狀態反映其內部特性。css
在BUI 1.6版本之前有沒有組件化呢?
先來看看組件包含什麼, 模板, 模塊, 樣式, 數據四個部分, BUI一直有組件化, 單頁就是一個組件, 一個單頁由一個同名html(包含樣式),js組成. 移動開發因爲頁面較小, 把一個頁面當作是一個大的組件, 組件裏面會結合多個控件, 比方選項卡,輪播圖,列表刷新等. html
比方下面例子:前端
bui.load({ url:"pages/list/index.html" })
跳轉到列表頁面, 咱們即可知道該目錄下還有 pages/list/index.js
文件來處理業務, 默認的模塊名爲pages/list/index
. 最簡單的路由, 一切無需配置.vue
pages/list/index.htmljava
<div class="bui-page bui-box-vertical"> <header></header> <main> <!-- 輪播圖 --> <div id="slide" class="bui-slide"></div> </main> <footer></footer> </div>
pages/list/index.jsnode
loader.define(function(require,export,module){ // 業務代碼 var pageview = { init: function(){ // 輪播圖控件初始化 var uiSlide = bui.slide({ id:"#slide", height: 300, data: [{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }] }) } } // 頁面跳轉便執行 pageview.init(); return pageview; })
路由跳轉內部作了什麼?jquery
// 加載模板 loader.import("pages/list/index.html",function(res){ // id 指向動態建立的路由頁面id $("#id").html(res); // 執行js模塊, 若是該模塊沒有被建立過, 會自動執行 loader.require("pages/list/index") })
只是簡單示例說明, 實際作了更多複雜的處理. 單頁的開發模塊裏面,$
選擇器要替換成router.$
選擇器, 若是頁面重複被加載進來,$
從document
查找會致使找到多個相同ID,router.$
則限制了只在當前頁面.
隨着業務的深刻, 單頁組件裏面承載了較多業務邏輯, 很差維護. 上面的例子咱們看到,
pages/list/index
模塊裏面, 初始化了一個控件, 一個頁面若是隻有一個控件, 那也沒什麼, 但每每不止這些, 咱們可能頁面還有TAB, 每一個TAB裏面就有一個輪播圖組件, 那咱們就要區分不一樣的ID初始化不一樣的輪播圖了. 若是把輪播圖抽離成一個單獨的組件, 這部分業務就能夠抽離出來.
咱們新建了一個目錄 components
用來存放這些抽離的組件.git
輪播圖模板
pages/components/slide/index.htmlgithub
<div class="bui-slide"></div>
id="slide"
這個屬性咱們去掉了,若是模板包含id,意味着建立出來的組件會有多個相同id.
輪播圖組件定義
pages/components/slide/index.js
loader.define(function(require,export,module){ // 接收`component` 標籤上的屬性參數 var params = bui.history.getParams(module.id); // 輪播圖控件初始化 var uiSlide = bui.slide({ // 經過父層的id 找到當前的 bui-slide id:`#${module.id} .bui-slide`, height: 300, data: [{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }] }) return uiSlide; })
輪播圖組件加載, component
標籤若是無id屬性, 會自動建立一個隨機guid
, 也就是組件內部獲取到的 module.id
pages/list/index.html
<div class="bui-page bui-box-vertical"> <header></header> <main> <!-- 新聞輪播圖 type 爲自定義屬性,用於區分不一樣數據 --> <component name="pages/components/slide/index" type="news"></component> <!-- 視頻輪播圖 --> <component name="pages/components/slide/index" type="video"></component> </main> <footer></footer> </div>
輪播圖樣式定義
樣式沒有獨立的做用域, 要防止跟其它樣式衝突, 那組件須要一個獨立的樣式名.
<style> .slide-skin .bui-slide-main {} </style> <div class="bui-slide slide-skin"></div>
組件包含數據,以確保該組件能正常運行, 咱們能夠把輪播圖的組件再進行優化.
抽離輪播圖測試數據, 示例數據
pages/components/slide/index.json
[{ image:"images/slide01.jpg" },{ image:"images/slide02.jpg" }]
完整的輪播圖組件
pages/components/slide/index.js
loader.define(function(require,export,module){ // 接收`component` 標籤上的屬性參數 var params = bui.history.getParams(module.id); // 輪播圖控件初始化 var uiSlide = bui.slide({ // 經過父層的id 找到當前的 bui-slide id:`#${module.id} .bui-slide`, height: 300, data: [] }) // 經過不一樣參數請求區分不一樣數據 bui.ajax({ // 模塊在被加載或者被移到其它路徑下, 都不會影響到這個路徑的地址. url:`${module.path}/index.json`, data:{ // 請求接口的不一樣類型 type: params.type }, success: function(res){ // 修改輪播圖數據 uiSlide.option("data",res); } }) return uiSlide; })
組件預覽:
地址欄上輸入如下地址即可預覽組件效果.index.html#pages/components/slide/index
模擬屬性傳參
在地址上加上參數
index.html#pages/components/slide/index?type=news
BUI 組件有3種表現形式, 路由的跳轉是頁面組件,component
標籤加載是一種局部組件,bui.page
是彈出加載組件, 層級最高.
比方: 點擊個人, 須要在當前頁插入一個登陸頁面.
pages/main/main.js
loader.define(function(require,export,module){ var pageveiw = { init: function(){ // 初始化 this.tab = this.tabInit(); }, isLogin: false, pageLogin: null, tabInit: function(){ var that = this; var tab = bui.tab({ id: "#tab" }); // tab 的滑動,點擊,都會觸發 to 事件. tab.on("to",function(){ var index = this.index(); // 若是跳轉到第3個,而且未登陸, 則插入登陸頁. if( index === 3 && !that.isLogin ){ if( that.pageLogin ){ // 第二次打開就好 that.pageLogin.open(); return; } // 第一次初始化 that.pageLogin = bui.page({ url:"pages/login/index.html", // 告訴登陸頁, 是從tab的第三個跳轉過去的, 那登陸回來之後就能夠再跳轉到第三個Tab. param: { type: "tab", index: 3 } }) } }) return tab; } } // 初始化 pageveiw.init(); return pageview; })
pages/login/index.js
loader.define(function(require,export,module){ var parasm = bui.history.getParams(module.id); var pageview = { init: function(){ this.bind(); }, bind: function(){ router.$("#btnLogin").click(function(){ // 檢測登陸是否成功, 是則跳轉回上一個頁面, 而且觸發to事件 // 主動關閉 // var dialog = bui.history.getPageDialog(module.id); // dialog.close(); bui.back(function(mod){ // 關閉彈窗 mod.pageLogin && mod.pageLogin.close(); // 修改登陸狀態 mod.isLogin = true; // 拿到上一個模塊,調用tab實例的to方法, 跳到第3各索引, 觸發 監聽的on事件. mod.tab.to(parasm.index) }) }) } } // 初始化 pageview.init(); return pageview; })
做爲登陸頁面組件, 就須要處理多種類型, 比方從路由跳轉的, 比方以組件層的方式加載的, 那分別要作什麼事情?
這個登陸的完整示例工程能夠在 BUI的3種權限登陸 裏面找到. tablogin2
工程.
實例分發實際上是bui.store
的一個mixins
參數, 這個跟vue
的混入是同樣的. 適合處理比較複雜的頁面, 把模塊分發出去, 便於維護, 跟組件是同樣的道理, 但這個是分離的.
比方有個詳情頁面, 詳情裏面有表單, 正文, 附件.
這裏咱們使用 bui.store
來實現. 案例的預覽地址 實例分發
詳情模板
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <a class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></a> </div> <div class="bui-bar-main">詳情</div> <div class="bui-bar-right"> </div> </div> <ul id="floorNav" class="bui-nav bui-nav-skin01"> <li class="bui-btn active">表單</li> <li class="bui-btn">正文</li> <li class="bui-btn">附件(2)</li> </ul> </header> <main> <div id="floor" class="bui-floor"> <div class="bui-floor-main container-y"> <div class="panel-list bui-interval"> <!-- 表單 --> <view name="pages/store/views/form/index"></view> <!-- 正文 --> <view name="pages/store/views/article/index"></view> <!-- 附件 --> <view name="pages/store/views/attach/index"></view> </div> </div> <div class="bui-floor-foot"></div> </div> </main> </div>
詳情模塊
pages/detail/index.js
loader.define([ "pages/store/views/form/index", "pages/store/views/article/index", "pages/store/views/attach/index" ], function(form, article, attach, require, exports, module) { // 初始化數據行爲存儲 var bs = bui.store({ el: ".bui-page", scope: "page", data: { title: "測試標題" }, mixins: [form, article, attach], methods: {}, watch: {}, computed: {}, templates: {}, beforeMount: function() { // 數據解析前執行, 修改data的數據示例 // this.$data.a = 2 }, mounted: function() { var that = this; // 數據解析後執行 var floor = bui.floor({ id: "#floor", menu: "#floorNav", floorItem: "view" }) } }) return bs; })
表單模板:
pages/store/views/form/index.html
<style> .panel-form .bui-list .bui-btn { border-bottom: 0; } </style> <div class="bui-panel panel-form bui-floor-item"> <div class="bui-panel-head" name="page">表單</div> <div class="bui-panel-main container-xy" text="page" b-template="page.tplForm(page.formData)"> </div> </div>
表單模塊:
pages/store/views/form/index.js
loader.define(function(require,exports,module) { // 在這裏初始化控件 var pageview = { data: { formData: { title:"《廣州XXX2020年年中預算審批》", phone: "13800138000" } }, methods: { callhim: function(phone){ // 打電話 bui.unit.tel(phone); } }, templates: { tplForm: function(data) { var html = ""; html += `<ul class="bui-list list-form"> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">標題</label> <div class="span1"> <div class="bui-value">${data.title}</div> </div> </li> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">電話</label> <div class="span1"> <div class="bui-value phone" b-click="page.callhim2(${data.phone})"> <b>${data.phone}</b><i class="icon-phone"></i> </div> </div> </li> ... </ul>`; return html; } }, mounted: function(param) { console.log("mounted form") } }; // 拋出模塊 return pageview; })
其它組件相似, 返回一個對象, 最終在詳情的實例上合併. 這種分發只是業務的拆分, 並沒有獨立做用域. 若是須要獨立做用域, 則應該改成如下加載.
詳情模板
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> ... </header> <main> <div id="floor" class="bui-floor"> <div class="bui-floor-main container-y"> <div class="panel-list bui-interval"> <!-- 表單 --> <component name="pages/store/views/form/index"></component> <!-- 正文 --> <view name="pages/store/views/article/index"></view> <!-- 附件 --> <view name="pages/store/views/attach/index"></view> </div> </div> <div class="bui-floor-foot"></div> </div> </main> </div>
詳情模塊
pages/detail/index.js
loader.define([ "pages/store/views/article/index", "pages/store/views/attach/index" ], function(article, attach, require, exports, module) { // 初始化數據行爲存儲 var bs = bui.store({ el: ".bui-page", scope: "page", data: { title: "測試標題" }, mixins: [article, attach], methods: {}, watch: {}, computed: {}, templates: {}, beforeMount: function() { // 數據解析前執行, 修改data的數據示例 // this.$data.a = 2 }, mounted: function() { var that = this; // 數據解析後執行 var floor = bui.floor({ id: "#floor", menu: "#floorNav", floorItem: "view" }) } }) return bs; })
表單模板:
pages/store/views/form/index.html
scope
改成了form
<style> .panel-form .bui-list .bui-btn { border-bottom: 0; } </style> <div class="bui-panel panel-form bui-floor-item"> <div class="bui-panel-head" name="page">表單</div> <div class="bui-panel-main container-xy" text="page" b-template="form.tplForm(form.formData)"> </div> </div>
表單模塊
pages/store/views/form/index.js
loader.define(function(require,exports,module) { // 在這裏初始化控件 var bs = bui.store({ el: `#${module.id}`, scope: "form", data: { formData: { title:"《廣州XXX2020年年中預算審批》", phone: "13800138000" } }, methods: { callhim: function(phone){ // 打電話 bui.unit.tel(phone); } }, templates: { tplForm: function(data) { var html = ""; html += `<ul class="bui-list list-form"> <li class="bui-btn clearactive bui-box-align-top"> <label class="bui-label">標題</label> <div class="span1"> <div class="bui-value">${data.title}</div> </div> </li> ... </ul>`; return html; } }, mounted: function(param) { console.log("mounted form") } }); // 拋出模塊 return bs; })
在使用單頁路由初始化之後, 咱們便有了一個歷史記錄router.history
, 新版1.6之後, 把router.history
抽離出來, 經過bui.history
去訪問. 這樣不管是單頁開發, 仍是多頁開發, 都能經過bui.history
去獲取實例及參數. 而且在這個對象裏面, 頁面傳參,組件傳參,page傳參, 均可以在這個歷史記錄裏面獲取到之間的依賴關係.組件的加載是一條線, 線的末端能夠操控線的前端.
var allHistory = bui.history.get(); // allHistory 默認的歷史記錄 [{ component: {}, effect: "push", exports: {}, id: "buib7522-dc12-3f33-cdb5-29122a2cf1f6", name: "pages/store/view", page: {}, param: {}, replace: false, syncHistory: true, toggle: null, url: "pages/store/view.html" }]
pages/detail/index.html
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>BUI多頁開發示例</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/buijs/lib/latest/bui.css"> </head> <body> <div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <div class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></div> </div> <div class="bui-bar-main"> 多頁加載組件 </div> <div class="bui-bar-right"> </div> </div> </header> <main> <!-- 加載輪播圖組件 --> <component name="pages/components/slide/index"></component> </main> </div> <script src="https://cdn.jsdelivr.net/npm/buijs/lib/zepto.js"></script> <script src="https://cdn.jsdelivr.net/npm/buijs/lib/latest/bui.js"></script> <script src="index.js"></script> </body> </html>
多頁的初始化
pages/detail/index.js
bui.ready(function(){ // 初始化 var allHistory = bui.history.getLast(); // 多頁開發的歷史記錄, 永遠只有一個. 頁面跟頁面之間沒法交互, 可是頁面跟組件跟組件層之間的交互是沒問題的. })
pages/detail/index.html
<div class="bui-page bui-box-vertical"> <header> <div class="bui-bar"> <div class="bui-bar-left"> <div class="bui-btn" onclick="bui.back();"><i class="icon-back"></i></div> </div> <div class="bui-bar-main"> 單頁加載組件 </div> <div class="bui-bar-right"> </div> </div> </header> <main> <!-- 加載輪播圖組件 --> <component name="pages/components/slide/index"></component> </main> </div>
pages/detail/index.js
loader.define(function(require,export,module){ // 獲取最後一條歷史記錄 var currentHistory = bui.history.getLast(); })
同樣的組件代碼, 除了腳本模塊的定義不一樣之外. 多頁簡單, 單頁則在體驗,跟操控上會有更多靈活空間. 能夠根據須要自行選擇.
推薦從新安裝
buijs
cli工具. 記得關閉360等一切會阻止C盤寫入的程序.
npm install -g buijs
// 所有權限示例 buijs create -t case-indexlogin // 部分權限示例 buijs create -t case-tablogin // 163的組件化示例 buijs create -t case-163
gitee
, 國內構建的速度會快不少.結合新版出了一些快速書寫, 建議更新, 若是使用 vscode
只需在插件搜索 bui-fast
即可.
新增了一些方法及控件, 其它更新了控件的一些問題, 就不一一列舉了, 感興趣能夠看看官網的changelog
碼字不易, 歡迎關注bui神速
, 跟咱們一塊兒交流移動開發的問題, 常見問題還請搜索官方文檔, 咱們會不按期更新一些技巧.