摘要:自定義組件的自定義組件,是基于微信小程序框架的組件之上,進(jìn)一步自定義組合,擁有邏輯處理和樣式。這樣做的目的請參見微信小程序開發(fā)三宗罪和解決方案項(xiàng)目中通用自定義組件存放在目錄,一個(gè)組件一般由三個(gè)文件組成,和分別對應(yīng)微信小程序框架的和文件。
Labrador 是一個(gè)專為微信小程序開發(fā)的組件化開發(fā)框架。
特性使用Labrador框架可以使微信開發(fā)者工具支持加載海量NPM包
支持ES6/7標(biāo)準(zhǔn)代碼,使用async/await能夠有效避免回調(diào)地獄
組件重用,對微信小程序框架進(jìn)行了二次封裝,實(shí)現(xiàn)了組件重用和嵌套
自動(dòng)化測試,非常容易編寫單元測試腳本,不經(jīng)任何額外配置即可自動(dòng)化測試
使用Editor Config及ESLint標(biāo)準(zhǔn)化代碼風(fēng)格,方便團(tuán)隊(duì)協(xié)作
安裝首先您的系統(tǒng)中安裝Node.js和npm v3 下載Node.js,然后運(yùn)行下面的命令將全局安裝Labrador命令行工具。
npm install -g labrador-cli初始化項(xiàng)目
mkdir demo # 新建目錄 cd demo # 跳轉(zhuǎn)目錄 npm init # 初始化npm包 labrador init # 初始化labrador項(xiàng)目項(xiàng)目目錄結(jié)構(gòu)
demo # 項(xiàng)目根目錄 ├── .labrador # Labrador項(xiàng)目配置文件 ├── .babelrc # babel配置文件 ├── .editorconfig # Editor Config ├── .eslintignore # ESLint 忽略配置 ├── .eslintrc # ESLint 語法檢查配置 ├── package.json ├── dist/ # 目標(biāo)目錄 ├── node_modules/ └── src/ # 源碼目錄 ├── app.js ├── app.json ├── app.less ├── components/ # 通用組件目錄 ├── pages/ # 頁面目錄 └── utils/
配置開發(fā)工具注意 dist目錄中的所有文件是由labrador命令編譯生成,請勿直接修改
項(xiàng)目初始化后使用WebStorm或Sublime等你習(xí)慣的IDE打開項(xiàng)目根目錄。然后打開 微信web開發(fā)者工具 新建項(xiàng)目,本地開發(fā)目錄選擇 dist 目標(biāo)目錄。
開發(fā)流程在WebStorm或Sublime等IDE中編輯 src 目錄下的源碼,然后在項(xiàng)目根目錄中運(yùn)行labrador build 命令構(gòu)建項(xiàng)目,然后在 微信web開發(fā)者工具 的調(diào)試界面中點(diǎn)擊左側(cè)菜單的 重啟 按鈕即可查看效果。
我們在開發(fā)中, 微信web開發(fā)者工具 僅僅用來做調(diào)試和預(yù)覽,不要在 微信web開發(fā)者工具 的編輯界面修改代碼。
微信web開發(fā)者工具 會偶爾出錯(cuò),表現(xiàn)為點(diǎn)擊 重啟 按鈕沒有反應(yīng),調(diào)試控制臺輸出大量的無法require文件的錯(cuò)誤,編輯 界面中代碼文件不顯示。這是因?yàn)?labrador build 命令會更新整個(gè) dist 目錄,而 微信web開發(fā)者工具 在監(jiān)測代碼改變時(shí)會出現(xiàn)異常,遇到這種情況只需要關(guān)掉 微信web開發(fā)者工具 再啟動(dòng)即可。
我們還可以使用 labrador watch 命令來監(jiān)控 src 目錄下的代碼,當(dāng)發(fā)生改變后自動(dòng)構(gòu)建,不用每一次編輯代碼后手動(dòng)運(yùn)行 labrador build 。
所以最佳的姿勢是:
在項(xiàng)目中運(yùn)行 labrador watch
在WebStorm中編碼,保存
切換到 微信web開發(fā)者工具 中調(diào)試、預(yù)覽
再回到WebStorm中編碼
...
labrador 命令 labrador init 初始化項(xiàng)目命令注意此命令會初始化當(dāng)前的目錄為項(xiàng)目目錄。
labrador build 構(gòu)建當(dāng)前項(xiàng)目Usage: labrador build [options] Options: -h, --help output usage information -V, --version output the version number -c, --catch 在載入時(shí)自動(dòng)catch所有JS腳本的錯(cuò)誤 -t, --test 運(yùn)行測試腳本 -d, --debug DEBUG模式 -m, --minify uglify壓縮代碼labrador watch 監(jiān)測文件變化
Usage: labrador watch [options] Options: -h, --help output usage information -V, --version output the version number -c, --catch 在載入時(shí)自動(dòng)catch所有JS腳本的錯(cuò)誤 -t, --test 運(yùn)行測試腳本 -d, --debug DEBUG模式 -m, --minify uglify壓縮代碼labrador 庫
labrador 庫對全局的 wx 變量進(jìn)行了封裝,將大部分 wx 對象中的方法進(jìn)行了Promise支持, 除了以 on* 開頭或以 *Sync 結(jié)尾的方法。在如下代碼中使用 labrador 庫。
import wx from "labrador"; console.log(wx.version); wx.app; // 和全局的 getApp() 函數(shù)效果一樣,代碼風(fēng)格不建議粗暴地訪問全局對象和方法 wx.Component; // Labrador 自定義組件基類 wx.Types; // Labrador 數(shù)據(jù)類型校驗(yàn)器集合 wx.login; // 封裝后的微信登錄接口 wx.getStorage; // 封裝后的讀取緩存接口 //... 更多請參見 https://mp.weixin.qq.com/debug/wxadoc/dev/api/
我們建議不要再使用 wx.getStorageSync() 等同步阻塞方法,而在 async 函數(shù)中使用 await wx.getStorage() 異步非阻塞方法提高性能,除非遇到特殊情況。
app.jssrc/app.js 示例代碼如下:
import wx from "labrador"; import {sleep} from "./utils/util"; export default class { globalData = { userInfo: null }; async onLaunch() { //調(diào)用API從本地緩存中獲取數(shù)據(jù) let res = await wx.getStorage({ key: "logs" }); let logs = res.data || []; logs.unshift(Date.now()); await wx.setStorage({ key: "logs", data: logs }); this.timer(); } async timer() { while (true) { console.log("hello"); await sleep(10000); } } async getUserInfo() { if (this.globalData.userInfo) { return this.globalData.userInfo; } await wx.login(); let res = await wx.getUserInfo(); this.globalData.userInfo = res.userInfo; return res.userInfo; } }
代碼中全部使用ES6/7標(biāo)準(zhǔn)語法。代碼不必聲明 use strict ,因?yàn)樵诰幾g時(shí),所有代碼都會強(qiáng)制使用嚴(yán)格模式。
代碼中并未調(diào)用全局的 App() 方法,而是使用 export 語法默認(rèn)導(dǎo)出了一個(gè)類,在編譯后,Labrador會自動(dòng)增加 App() 方法調(diào)用,所有請勿手動(dòng)調(diào)用 App() 方法。這樣做是因?yàn)榇a風(fēng)格不建議粗暴地訪問全局對象和方法。
自定義組件Labrador的自定義組件,是基于微信小程序框架的組件之上,進(jìn)一步自定義組合,擁有邏輯處理和樣式。這樣做的目的請參見 微信小程序開發(fā)三宗罪和解決方案
項(xiàng)目中通用自定義組件存放在 src/compontents 目錄,一個(gè)組件一般由三個(gè)文件組成,*.js 、 *.xml 和 *.less 分別對應(yīng)微信小程序框架的 js 、 wxml 和 wxss 文件。在Labardor項(xiàng)目源碼中,我們特意采用了 xml 和 less 后綴以示區(qū)別。如果組件包含單元測試,那么在組件目錄下會存在一個(gè) *.test.js 的測試腳本文件。
自定義組件示例下面是一個(gè)簡單的自定義組件代碼實(shí)例:
import wx from "labrador"; import randomColor from "../../utils/random-color"; const { string } = wx.Types; export default class Title extends wx.Component { propTypes = { text: string }; props = { text: "" }; data = { text: "", color: randomColor() }; onUpdate(props) { this.setData("text", props.text); } handleTap() { this.setData({ color: randomColor() }); } }
自定義組件的邏輯代碼和微信框架中的page很相似,最大的區(qū)別是在js邏輯代碼中,沒有調(diào)用全局的 Page() 函數(shù)聲明頁面,而是用 export 語法導(dǎo)出了一個(gè)默認(rèn)的類,這個(gè)類必須繼承于 labrador.Component 組件基類。
相對于微信框架中的page,Labrador自定義組件擴(kuò)展了 propTypes 、 props 、 children 選項(xiàng)及 onUpdate 生命周期函數(shù)。children 選項(xiàng)代表當(dāng)前組件中的子組件集合,此選項(xiàng)將在下文中敘述。
Labrador的目標(biāo)是構(gòu)建一個(gè)可以重用、嵌套的自定義組件方案,在現(xiàn)實(shí)情況中,當(dāng)多個(gè)組件互相嵌套組合,就一定會遇到父子組件件的數(shù)據(jù)和消息傳遞。因?yàn)樗械慕M件都實(shí)現(xiàn)了 setData 方法,所以我們可以使用 this.children.foobar.setData(data) 或 this.parent.setData(data) 這樣的代碼調(diào)用來解決父子組件間的數(shù)據(jù)傳遞問題,但是,如果項(xiàng)目中出現(xiàn)大量這樣的代碼,那么數(shù)據(jù)流將變得非?;靵y。
我們借鑒了 React.js 的思想,為組件增加了 props 機(jī)制。子組件通過 this.props 得到父組件給自己傳達(dá)的參數(shù)數(shù)據(jù)。父組件怎樣將數(shù)據(jù)傳遞給子組件,我們下文中敘述。
onUpdate 生命周期函數(shù)是當(dāng)組件的 props 發(fā)生變化后被調(diào)用,類似React.js中的 componentWillReceiveProps 所以我們可以在此函數(shù)體內(nèi)監(jiān)測 props 的變化。
組件定義時(shí)的 propTypes 選項(xiàng)是對當(dāng)前組件的props參數(shù)數(shù)據(jù)類型的定義。 props 選項(xiàng)代表的是當(dāng)前組件默認(rèn)的各項(xiàng)參數(shù)值。propTypes 、 props 選項(xiàng)都可以省略,但是強(qiáng)烈建議定義 propTypes,因?yàn)檫@樣可以使得代碼更清晰易懂,另外還可以通過Labrador自動(dòng)檢測props值類型,以減少BUG。為優(yōu)化性能,只有在DEBUG模式下才會自動(dòng)檢測props值類型。
編譯時(shí)加上 -d 參數(shù)時(shí)即可進(jìn)入DEBUG模式,在代碼中任何地方都可以使用魔術(shù)變量 __DEBUG__ 來判斷是否是DEBUG模式。
另外,Labrador自定義組件的 setData 方法,支持兩種傳參方式,第一種像微信框架一樣接受一個(gè) object 類型的對象參數(shù),第二種方式接受作為KV對的兩個(gè)參數(shù),setData 方法將自動(dòng)將其轉(zhuǎn)為 object。
注意 組件中事件響應(yīng)方法必須以 handle 開頭!例如上文中的 handleTap,否則子組件將無法與模板綁定。這樣做也是為了代碼風(fēng)格統(tǒng)一,方便團(tuán)隊(duì)協(xié)作。建議事件響應(yīng)方法命名采用 handle + 組件名 + 事件名 例如:handleUsernameChange handleLoginButtonTap ,這樣我們很容易區(qū)分是模板上哪一個(gè)組件發(fā)生了什么事件,如果省略中間的名詞,如 handleTap ,則代表當(dāng)前整個(gè)自定義組件發(fā)生了 tap 事件。
{{text}}
XML布局文件和微信WXML文件語法完全一致,只是擴(kuò)充了一個(gè)自定義標(biāo)簽
.title-text { font-weight: bold; font-size: 2em; }
雖然我們采用了LESS文件,但是由于微信小程序框架的限制,不能使用LESS的層級選擇及嵌套語法。但是我們可以使用LESS的變量、mixin、函數(shù)等功能方便開發(fā)。
頁面我們要求所有的頁面必須存放在 pages 目錄中,每個(gè)頁面的子目錄中的文件格式和自定義組件一致,只是可以多出一個(gè) *.json 配置文件。
頁面示例下面是默認(rèn)首頁的示例代碼:
import wx from "labrador"; import List from "../../components/list/list"; import Title from "../../components/title/title"; import Counter from "../../components/counter/counter"; export default class Index extends wx.Component { data = { userInfo: {}, mottoTitle: "Hello World", count: 0 }; get children() { return { list: new List(), motto: new Title({ text: "@mottoTitle" }), counter: new Counter({ count: "@count", onChange: this.handleCountChange }) }; } async onLoad() { try { //調(diào)用應(yīng)用實(shí)例的方法獲取全局?jǐn)?shù)據(jù) let userInfo = await wx.app.getUserInfo(); //更新數(shù)據(jù) this.setData({ userInfo }); this.update(); } catch (error) { console.error(error.stack); } } onReady() { this.setData("mottoTitle", "Labrador"); } handleCountChange(count) { this.setData({ count }); } //事件處理函數(shù) handleViewTap() { wx.navigateTo({ url: "../logs/logs" }); } }
頁面代碼的格式和自定義組件的格式一模一樣,我們的思想是 頁面也是組件。
js邏輯代碼中同樣使用 export default 語句導(dǎo)出了一個(gè)默認(rèn)類,也不能手動(dòng)調(diào)用 Page() 方法,因?yàn)樵诰幾g后,pages 目錄下的所有js文件全部會自動(dòng)調(diào)用 Page() 方法聲明頁面。
我們看到組件類中,有一個(gè)對象屬性 children ,這個(gè)屬性定義了該組件依賴、包含的其他自定義組件,在上面的代碼中頁面包含了三個(gè)自定義組件 list 、 title 和 counter ,這個(gè)三個(gè)自定義組件的 key 分別為 list 、 motto 和 counter。
自定義組件類在實(shí)例化時(shí)接受一個(gè)類型為 object 的參數(shù),這個(gè)參數(shù)就是父組件要傳給子組件的props數(shù)據(jù)。一般情況下,父組件傳遞給子組件的props屬性在其生命周期中是不變的,這是因?yàn)镴S的語法和小程序框架的限制,沒有React.js的JSX靈活。但是我們可以傳遞一個(gè)以 @ 開頭的屬性值,這樣我們就可以把子組建的 props 屬性值綁定到父組件的 data 上來,當(dāng)父組件的 data 發(fā)生變化后,Labrador將自動(dòng)更新子組件的 props。例如上邊代碼中,將子組件 motto 的 text 屬性綁定到了 @mottoTitle。那么在 onReady 方法中,將父組件的 mottoTitle 設(shè)置為 Labrador,那么子組件 motto 的 text 屬性就會自動(dòng)變?yōu)?Labrador。
頁面也是組件,所有的組件都擁有一樣的生命周期函數(shù)onLoad, onReady, onShow, onHide, onUnload,onUpdate 以及setData函數(shù)。
componets 和 pages 兩個(gè)目錄的區(qū)別在于,componets 中存放的組件能夠被智能加載,pages 目錄中的組件在編譯時(shí)自動(dòng)加上 Page() 調(diào)用,所以,pages 目錄中的組件不能被其他組件調(diào)用,否則將出現(xiàn)多次調(diào)用Page()的錯(cuò)誤。如果某個(gè)組件需要重用,請存放在 componets 目錄或打包成NPM包。
{{ userInfo.nickName }}
XML布局代碼中,使用了Labrador提供的
@import "list"; @import "title"; @import "counter"; .motto-title-text { font-size: 3em; padding-bottom: 1rem; } /* ... */
LESS樣式文件中,我們使用了 @import 語句加載所有子組件樣式,這里的 @import "list" 語句按照LESS的語法,會首先尋找當(dāng)前目錄 src/pages/index/ 中的 list.less 文件,如果找不到就會按照Labrador的規(guī)則智能地嘗試尋找 src/componets 和 node_modules 目錄中的組件樣式。
接下來,我們定義了 .motto-title-text 樣式,這樣做是因?yàn)?motto key 代表的title組件的模板中(src/compontents/title/title.xml)有一個(gè)view 屬于 title-text 類,編譯時(shí),Labrador將自動(dòng)為其增加一個(gè)前綴 motto- ,所以編譯后這個(gè)view所屬的類為 title-text motto-title-text (可以查看 dist/pages/index/index.xml)。那么我們就可以在父組件的樣式代碼中使用 .motto-title-text 來重新定義子組件的樣式。
Labrador支持多層組件嵌套,在上述的實(shí)例中,index 包含子組件 list 和 title,list 包含子組件 title,所以在最終顯示時(shí),index 頁面上回顯示兩個(gè) title 組件。
詳細(xì)代碼請參閱 labrador init 命令生成的示例項(xiàng)目。
自動(dòng)化測試我們規(guī)定項(xiàng)目中所有后綴為 *.test.js 的文件為測試腳本文件。每一個(gè)測試腳本文件對應(yīng)一個(gè)待測試的JS模塊文件。例如 src/utils/util.js 和 src/utils/utils.test.js 。這樣,項(xiàng)目中所有模塊和其測試文件就全部存放在一起,方便查找和模塊劃分。這樣規(guī)劃主要是受到了GO語言的啟發(fā),也符合微信小程序一貫的目錄結(jié)構(gòu)風(fēng)格。
在編譯時(shí),加上 -t 參數(shù)即可自動(dòng)調(diào)用測試腳本完成項(xiàng)目測試,如果不加 -t 參數(shù),則所有測試腳本不會被編譯到 dist 目錄,所以不必?fù)?dān)心項(xiàng)目會肥胖。
普通JS模塊測試測試腳本中使用 export 語句導(dǎo)出多個(gè)名稱以 test* 開頭的函數(shù),這些函數(shù)在運(yùn)行后會被逐個(gè)調(diào)用完成測試。如果test測試函數(shù)在運(yùn)行時(shí)拋出異常,則視為測試失敗,例如代碼:
// src/util.js // 普通項(xiàng)目模塊文件中的代碼片段,導(dǎo)出了一個(gè)通用的add函數(shù) export function add(a, b) { return a + b; }
// src/util.test.js // 測試腳本文件代碼片段 import assert from "assert"; //測試 util.add() 函數(shù) export function testAdd(exports) { assert(exports.add(1, 1) === 2); }
代碼中 testAdd 即為一個(gè)test測試函數(shù),專門用來測試 add() 函數(shù),在test函數(shù)執(zhí)行時(shí),會將目標(biāo)模塊作為參數(shù)傳進(jìn)來,即會將 util.js 中的 exports 傳進(jìn)來。
自定義組件測試自定義組件的測試腳本中可以導(dǎo)出兩類測試函數(shù)。第三類和普通測試腳本一樣,也為 test* 函數(shù),但是參數(shù)不是 exports 而是運(yùn)行中的、實(shí)例化后的組件對象。那么我們就可以在test函數(shù)中調(diào)用組件的方法或則訪問組件的props 和 data 屬性,來測試行為。另外,普通模塊測試腳本是啟動(dòng)后就開始逐個(gè)運(yùn)行 test* 函數(shù),而組件測試腳本是當(dāng)組件 onReady 以后才會開始測試。
自定義組件的第二類測試函數(shù)是以 on* 開頭,和組件的生命周期函數(shù)名稱一模一樣,這一類測試函數(shù)不是等到組件 onReady 以后開始運(yùn)行,而是當(dāng)組件生命周期函數(shù)運(yùn)行時(shí)被觸發(fā)。函數(shù)接收兩個(gè)參數(shù),第一個(gè)為組件的對象引用,第二個(gè)為run 函數(shù)。比如某個(gè)組件有一個(gè) onLoad 測試函數(shù),那么當(dāng)組件將要運(yùn)行 onLoad 生命周期函數(shù)時(shí),先觸發(fā) onLoad 測試函數(shù),在測試函數(shù)內(nèi)部調(diào)用 run() 函數(shù),繼續(xù)執(zhí)行組件的生命周期函數(shù),run() 函數(shù)返回的數(shù)據(jù)就是生命周期函數(shù)返回的數(shù)據(jù),如果返回的是Promise,則代表生命周期函數(shù)是一個(gè)異步函數(shù),測試函數(shù)也可以寫為async 異步函數(shù),等待生命周期函數(shù)結(jié)束。這樣我們就可以獲取run()前后兩個(gè)狀態(tài)數(shù)據(jù),最后對比,來測試生命周期函數(shù)的運(yùn)行是否正確。
第三類測試函數(shù)與生命周期測試函數(shù)類似,是以 handle* 開頭,用以測試事件處理函數(shù)是否正確,是在對應(yīng)事件發(fā)生時(shí)運(yùn)行測試。例如:
// src/components/counter/counter.test.js export function handleTap(c, run) { let num = c.data.num; run(); let step = c.data.num - num; if (step !== 1) { throw new Error("計(jì)數(shù)器點(diǎn)擊一次應(yīng)該自增1,但是自增了" + step); } }
生命周期測試函數(shù)和事件測試函數(shù)只會執(zhí)行一次,自動(dòng)化測試的結(jié)果將會輸出到Console控制臺。
項(xiàng)目配置文件labrador init 命令在初始化項(xiàng)目時(shí),會在項(xiàng)目根目錄中創(chuàng)建一個(gè) .labrador 項(xiàng)目配置文件,如果你的項(xiàng)目是使用 labrador-cli 0.3 版本創(chuàng)建的,可以手動(dòng)增加此文件。
配置文件為JSON格式,默認(rèn)配置為:
{ "npmMap":{ }, "uglify":{ "mangle": [], "compress": { "warnings": false } } }
npmMap 屬性為NPM包映射設(shè)置,例如 {"underscore":"lodash"} 配置,如果你的源碼中有require("underscore") 那么編譯后將成為 require("lodash")。這樣做是為了解決小程序的環(huán)境限制導(dǎo)致一些NPM包無法使用的問題。比如我們的代碼必須依賴于包A,A又依賴于B,如果B和小程序不兼容,將導(dǎo)致A也無法使用。在這總情況下,我們可以Fork一份B,起名為C,將C中與小程序不兼容的代碼調(diào)整下,最后在項(xiàng)目配置文件中將B映射為C,那么在編譯后就會繞過B而加載C,從而解決這個(gè)問題。
uglify 屬性為 UglifyJs2 的壓縮配置,在編譯時(shí)附加 -m 參數(shù)即可對項(xiàng)目中的所有文件進(jìn)行壓縮處理。
ChangeLog 2016-10-09labrador 0.3.0
重構(gòu)自定義組件支持綁定子組件數(shù)據(jù)和事件
2016-10-12labrador 0.4.0
增加自定義組件props機(jī)制
自動(dòng)化測試
UglifyJS壓縮集成
NPM包映射
增加.labrador項(xiàng)目配置文件
貢獻(xiàn)者鄭州脈沖軟件科技有限公司
梁興臣
開源協(xié)議本項(xiàng)目依據(jù)MIT開源協(xié)議發(fā)布,允許任何組織和個(gè)人免費(fèi)使用。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/87963.html
摘要:是一個(gè)專為微信小程序開發(fā)的模塊化的前端開發(fā)框架在微信小程序開發(fā)三宗罪和解決方案一文中我向大家闡述了微信小程序開發(fā)的三個(gè)弊端,并提供了框架來解決這些弊端。注意雖然我們采用了文件,但是由于微信小程序框架的限制,不能使用的層級選擇及嵌套語法。 Labrador 是一個(gè)專為微信小程序開發(fā)的模塊化的前端開發(fā)框架 在微信小程序開發(fā)三宗罪和解決方案一文中我向大家闡述了微信小程序開發(fā)的三個(gè)弊端,并提供...
閱讀 2016·2021-11-23 09:51
閱讀 1270·2019-08-30 15:55
閱讀 1645·2019-08-30 15:44
閱讀 786·2019-08-30 14:11
閱讀 1176·2019-08-30 14:10
閱讀 946·2019-08-30 13:52
閱讀 2659·2019-08-30 12:50
閱讀 650·2019-08-29 15:04