微信小程序組件化開發(fā)框架文檔
2017年5月22日,微信小程序推出之時,限制非常多,現(xiàn)在越來越開放。微信小程序開發(fā)文檔也受到越來越多人的關(guān)注,下面從多個方面來談?wù)勎⑿判〕绦蜷_發(fā)文檔的一些內(nèi)容。
小程序框架wepy文檔
成品DEMO展示
一個是使用 wepy new demo
命令生成的標準demo
一個是基于wepy開發(fā)的手機充值的完整demo
一個是基于wepy開發(fā)的開源的仿微信的聊天界面 (源代碼下載)
以上三個demo均在安卓機和IOS機上運行過通。
快速入門
代碼規(guī)范:
1 . 變量與方法使用盡量使用駝峰式命名,避免使用 $
開頭。
以 $
開頭的方法或者屬性為框架內(nèi)建方法或者屬性,可以被使用,使用前請參考 API文檔 。
2 .入口,頁面,組件的命名后綴為 .wpy
。外鏈的文件可以是其它后綴。
請參考 wpy文件說明
3 .使用ES6語法開發(fā)。
框架在ES6下開發(fā),因此也需要使用ES6開發(fā)小程序,ES6中有大量的語法糖可以讓我們的代碼更加簡潔高效。
4 .使用Promise
框架默認對小程序提供的API全都進行了 Promis小程序第三方平臺e 處理,甚至可以直接使用async/await等新特性進行開發(fā)。
項目創(chuàng)建與使用
安裝wepy
以下安裝都通過npm安裝
注:在騰訊開發(fā)網(wǎng),因為npm需要使用代理才可以,沒使用npm代理的同學建議使用騰訊內(nèi)部工具tnmp代替npm安裝,tnpm的安裝與使用請參考 alsotan g的文章。
1 .安裝 wepy 命令行工具
npm install wepy-cli -g
2 .在開發(fā)目錄生成開發(fā)DEMO
wepy new myproject
3 .開發(fā)實時編譯
wepy build --watch
項目目錄結(jié)構(gòu)
dist node_modules src components com_a.wpy com_b.wpy pages index.wpy page2.wpy app.wpy package.json
開發(fā)使用說明
1 .使用微信開發(fā)者工具新建項目,本地開發(fā)選擇dist目錄。
2 .微信開發(fā)者工具 —> 項目 —> 關(guān)閉ES6轉(zhuǎn)ES5。
3 .本地項目根目錄運行wepy build --watch,開啟實時編譯。
主要解決問題:
1. 開發(fā)模式轉(zhuǎn)換
在原有的小程序的開發(fā)模式下進行再次封裝,更貼近于現(xiàn)有MVVM框架開發(fā)模式??蚣茉陂_發(fā)過程中參考了一些現(xiàn)在框架的一些特性,并且融入其中,以下是使用wep小程序制作y前后的代碼對比圖。
官方DEMO代碼:
//index.js //獲取應(yīng)用實例 var app = getApp() Page({ data: { motto: 'Hello World', userInfo: {} }, //事件處理函數(shù) bindViewTap: function() { console.log('button clicked') }, onLoad: function () { console.log('onLoad') } })
基于wepy的實現(xiàn):
import wepy from 'wepy'; export default class Index extends wepy.page { data = { motto: 'Hello World', userInfo: {} }; methods = { bindViewTap () { console.log('button clicked'); } }; onLoad() { console.log('onLoad'); }; }
2. 支持組件化開發(fā)
參見章節(jié): 組件
示例代碼:
// index.wpy <template> <view> <component id="pannel" path="pannel"></component> <component id="counter1" path="counter"></component> <component id="counter2" path="counter"></component> <component id="list" path="list"></component> </view> </template> <script> import wepy from 'wepy'; import List from '../components/list'; import Panel from '../components/panel'; import Counter from '../components/counter'; export default class Index extends wepy.page { config = { "navigationBarTitleText": "test" }; components = { panel: Panel, counter1: Counter, counter2: Counter, list: List }; } </script>
3. 支持加載外部NPM包。
在編譯過程當中,會遞歸遍歷代碼中的require然后將對應(yīng)依賴文件從node_modules當中拷貝出來,并且修改require為相對路徑,從而實現(xiàn)對外部NPM包的支持。如下圖:
4. 單文件模式,使得目錄結(jié)構(gòu)更加清晰。
官方目錄結(jié)構(gòu) 要求app必須有三個文件app.json,app.js,app.wxss,頁面有4個文件 index.json,index.js,index.wxml,index.wxss。而且文件必須同名。
所以使用wepy開發(fā)前后開發(fā)目錄對比如下:
官方DEMO:
project pages index index.json index.js index.wxml index.wxss log log.json log.wxml log.js log.wxss app.js app.json app.wxss
使用wepy框架后目錄結(jié)構(gòu):
project src pages index.wpy log.wpy app.wpy
默認使用babel編譯,支持ES6/7的一些新特性。
用戶可以通過修改wepy.config.js配置文件,配置自己熟悉的babel環(huán)境進行開發(fā)。默認開啟使用了一些新的特性如promise,async/await等等。
示例代碼:
import wepy from 'wepy'; export default class Index extends wepy.page { getData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve({data: 123}); }, 3000); }); }; async onLoad() { let data = await this.getData(); console.log(data.data); }; }
5. 針對原生API進行優(yōu)化
對現(xiàn)在API進行promise處理,同時修復一些現(xiàn)有API的缺陷,比如:wx.request并發(fā)問題等。
原有代碼:
onLoad = function () { var self = this; wx.login({ success: function (data) { wx.getUserInfo({ success: function (userinfo) { self.setData({userInfo: userinfo}); } }); } }); }
基于wepy實現(xiàn)代碼:
async onLoad() { await wx.login(); this.userInfo = await wx.getUserInfo(); }
在同時并發(fā)10個request請求測試時:
不使用wepy:
使用wepy后:
進階說明
wepy.config.js 配置文件說明
執(zhí)行wepy new demo后,會生成類似配置文件。
let prod = process.env.NODE_ENV === 'production'; module.exports = { "wpyExt": ".wpy", "babel": { "presets": [ "es2015", "stage-1" ], "plugins": [ "transform-export-extensions", "syntax-export-extensions", "transform-runtime" ] } }; if (prod) { // 壓縮sass module.exports['sass'] = {"outputStyle": "compressed"}; // 壓縮less module.exports['less'] = {"compress": true}; // 壓縮js module.exports.plugins = { 'UglifyJsPlugin': { filter: /\.js$/, config: { compress: { warning: false } } }, 'TestPlugin': { filter: /\.wxml$/, config: { } } }; }
wpyExt:缺省值為’.wpy’,IDE默認情況下不會對此文件類型高亮,此時可以修改所有文件為.vue后綴(因為與vue高亮規(guī)則一樣),然后將此選項修改為.vue,就能解決部分IDE代碼高亮問題。
sa小程序設(shè)計ss:sass編譯配置,參見 這里
less:less編譯配置,參見 這里
babel:babel編譯配置,參見 這里
plugins:plugins為1.1.6版本之后功能,目前支持js壓縮與圖片壓縮,持續(xù)開發(fā)……
wpy文件說明
wpy文件的編譯過程過下:
一個 .wpy
文件分為三個部分:
樣式 <style></style>
對應(yīng)原有wxss
模板 <template></template>
對應(yīng)原有wxml
代碼 <script></script>
對應(yīng)原有js
其中入口文件app.wpy不需要template,所以編譯時會被忽略。這三個標簽都支持type和src屬性,type決定了其代碼編譯過程,src決定是否外聯(lián)代碼,存在src屬性且有效時,忽略內(nèi)聯(lián)代碼,示例如下:
<style type="less" src="page1.less"></style> <template type="wxml" src="page1.wxml"></template> <script> // some code </script>
標簽對應(yīng) type 值如下表所示:
|標簽|type默認值|type支持值| |—|—|—| |style|css|css,less,sass(待完成)| |template|wxml|wxml,xml,html(待完成)| |script|js|js,TypeScript(待完成)|
script說明
程序入口app.wpy
<style type="less"> /** less **/ </style> <script> import wepy from 'wepy'; export default class extends wepy.app { config = { "pages":[ "pages/index/index" ], "window":{ "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" } }; onLaunch() { console.log(this); } } </script>
入口app.wpy繼承自wepy.app,包含一個config屬性和其全局屬性、方法、事件。其中config屬性對應(yīng)原有的app.json,編譯時會根據(jù)config生成app.json文件,如果需要修改config中的內(nèi)容,請使用系統(tǒng)提供API。
頁面index.wpy
<style type="less"> /** less **/ </style> <template type="wxml"> <view> </view> <component id="counter1" path="counter"></component> </template> <script> import wepy form 'wepy'; import Counter from '../components/counter'; export default class Index extends wepy.page { config = {}; components = {counter1: Counter}; data = {}; methods = {}; events = {}; onLoad() {}; // Other properties } </script>
頁面入口繼承自wepy.page,主要屬性說明如下:
組件com.wpy
<style type="less"> /** less **/ </style> <template type="wxml"> <view> </view> </template> <script> import wepy form 'wepy'; export default class Com extends wepy.component { components = {}; data = {}; methods = {}; events = {}; // Other properties } </script>
頁面入口繼承自wepy.component,屬性與頁面屬性一樣,除了不需要config以及頁面特有的一些小程序事件等等。
組件
在小程序中,可以利用 JS模塊化 和 wxml模板 ,對業(yè)務(wù)模塊進行劃分,實現(xiàn)如下效果:
但實際上不同的模塊代碼與事件交互都是在同一個頁面空間處理的,比如說 moduleA 和 moduleB 中同時存在一個 add 響應(yīng)事件時,就需要在 html 和 js 中分別定義為 moduleA_add,moduleB_add。業(yè)務(wù)模塊復雜之后就不利于開發(fā)和維護。
在wepy中,利用組件化的特性可以解決此類問題,如下圖:
ComA 和 ComB中間的數(shù)據(jù)與事件相互隔離,可以分別擁有自己的add事件。
組件引用
當頁面或者組件需要引入子組件時,需要在頁面或者script中的components給組件分配唯一id,并且在template中添加 <component>
標簽,如index.wpy
Index頁面引入A,B,C三個組件,同時組件A和B又有自己的子組件D,E,F(xiàn),G,H。
組件通信與交互
wepy.component基類提供三個方法$broadcast,$emit,$invoke,因此任一頁面或任一組件都可以調(diào)用上述三種方法實現(xiàn)通信與交互,如:
$this.$emit('some-event', 1, 2, 3, 4);
組件的事件監(jiān)聽需要寫在events屬性下,如:
import wepy form 'wepy'; export default class Com extends wepy.component { components = {}; data = {}; methods = {}; events = { 'some-event': ($event, ...args) { console.log(`${this.name} receive ${$event.name} from ${$event.source.name}`); } }; // Other properties }
1 . $broadcast
$broadcast事件是由父組件發(fā)起,所有子組件都會收到此廣播事件,除非事件被手動取消。事件廣播的順序為廣度優(yōu)先搜索順序,如果Page_Index發(fā)起一個$broadcast事件,那么接收到事件的先后順序為:A, B, C, D, E, F, G, H。如下圖:
2 .$emit
$emit與$broadcast正好相反,事件發(fā)起組件的父組件會依次接收到$emit事件,如上圖,如果E發(fā)起一個$emit事件,那么接收到事件的先后順序為:A, Page_Index。如下圖:
3 .$invoke
$i微信小程序 apinvoke是一個組件對另一個組件的直接調(diào)用,通過傳入的組件路徑找到相應(yīng)組件,然后再調(diào)用其方法。
如果想在Page_Index中調(diào)用組件A的某個方法:
this.$invoke('ComA', 'someMethod', 'someArgs');
如果想在組件A中調(diào)用組件G的某個方法:
this.$invoke('./../ComB/ComG', 'someMethod', 'someArgs');
混合
混合可以將組之間的可復用部分抽離,從而在組件中使用混合時,可以將混合的數(shù)據(jù),事件以及方法注入到組件之中?;旌戏址譃閮煞N:
-
默認式混合
-
兼容式混合
默認式混合
對于組件data數(shù)據(jù),components組件,events事件以及其它自定義方法采用 默認式混合 ,即如果組件未聲明該數(shù)據(jù),組件,事件,自定義方法等,那么將混合對象中的選項將注入組件這中。對于組件已聲明的選項將不受影響。
// mixins/test.js import wepy from 'wepy'; export default class TestMixin extends wepy.page { data = { foo: 'foo defined by page', bar: 'bar defined by testMix' }; methods: { tap () { console.log('mix tap'); } } } // pages/index.wpy import wepy from 'wepy'; import TestMixin from './mixins/test'; export default class Index extends wepy.mixin { data = { foo: 'foo defined by index' }; mixins = [TestMixin ]; onShow() { console.log(this.foo); // foo defined by index. console.log(this.bar); // foo defined by testMix. } }
兼容式混合
對于組件methods響應(yīng)事件,以及小程序頁面事件將采用兼容式混合,即先響應(yīng)組件本身響應(yīng)事件,然后再響應(yīng)混合對象中響應(yīng)事件。
// mixins/test.js import wepy from 'wepy'; export default class TestMixin extends wepy.page { methods = { tap () { console.log('mix tap'); } }; onShow() { console.log('mix onshow'); } } // pages/index.wpy import wepy from 'wepy'; import TestMixin from './mixins/test'; export default class Index extends wepy.mixin { mixins = [TestMixin]; methods = { tap () { console.log('index tap'); } }; onShow() { console.log('index onshow'); } } // index onshow // mix onshow // ----- when tap // index tap // mix tap
數(shù)據(jù)綁定
小程序數(shù)據(jù)綁定方式
小程序通過Page提供的setData方法去綁定數(shù)據(jù),如:
this.setData({title: 'this is title'});
因為小程序架構(gòu)本身原因,頁面渲染層和JS邏輯層分開的,setData操作實際就是JS邏輯層與頁面渲染層之間的通信,那么如果在同一次運行周期內(nèi)多次執(zhí)行setData操作時,那么通信的次數(shù)是一次還是多次呢?經(jīng)過跟小程序團隊確認后得知多次setData會執(zhí)行多次通信。
wepy數(shù)據(jù)綁定方式
wepy使用臟數(shù)據(jù)檢查對setData進行封裝,在函數(shù)運行周期結(jié)束時執(zhí)行臟數(shù)據(jù)檢查,一來可以不用關(guān)心頁面多次setData是否會有性能上的問題,二來可以更加簡潔去修改數(shù)據(jù)實現(xiàn)綁定,不用重復去寫setData方法。代碼如下:
this.title = 'this is title';
但需注意,在函數(shù)運行周期之外的函數(shù)里去修改數(shù)據(jù)需要手動調(diào)用$apply方法。如:
setTimeout(() => { this.title = 'this is title'; this.$apply(); }, 3000);
wepy臟數(shù)據(jù)檢查流程
在執(zhí)行臟數(shù)據(jù)檢查是,會通過this.$$phase標識當前檢查狀態(tài),并且會保證在并發(fā)的流程當中,只會有一個臟數(shù)據(jù)檢查流程在運行,以下是執(zhí)行臟數(shù)據(jù)檢查的流程圖:
其它優(yōu)化細節(jié)
1. wx.request 接收參數(shù)修改
點這里查看 官方文檔
// 官方 wx.request({ url: 'xxx', success: function (data) { console.log(data); } }); // wepy 使用方式 // request 接口從只接收Object變?yōu)榭山邮誗tring wx.request('xxxx').then((d) => console.log(d));
2. 優(yōu)化事件參數(shù)傳遞
點這里查看 官方文檔
// 官方 <view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view> Page({ tapName: function(event) { console.log(event.currentTarget.hi)// output: WeChat } }); // wepy 建議傳參方式 <view id="tapTest" data-wepy-params="1-wepy-something" bindtap="tapName"> Click me! </view> events: { tapName (event, id, title, other) { console.log(id, title, other)// output: 1, wepy, something } }
3. 改變數(shù)據(jù)綁定方式
保留setData方法,但不建議使用setData執(zhí)行綁定,修復傳入undefined的bug,并且修改入?yún)⒅С郑?/p>
-
this.setData(target, value)
-
this.setData(object)
點這里查看 官方文檔
// 官方 <view> {{ message }} </view> onLoad: function () { this.setData({message: 'hello world'}); } // wepy <view> {{ message }} </view> onLoad () { this.message = 'hello world'; }
4. 組件代替模板和模塊
點這里查看 官方文檔
// 官方 <!-- item.wxml --> <template name="item"> <text>{{text}}</text> </template> <!-- index.wxml --> <import src="item.wxml"/> <template is="item" data="{{text: 'forbar'}}"/> <!-- index.js --> var item = require('item.js') // wepy <!-- /components/item.wpy --> <text>{{text}}</text> <!-- index.wpy --> <template> <component id="item"></component> </template> <script> import wepy from 'wepy'; import Item from '../componen制作小程序ts/item'; export default class Index extends wepy.page { components = { Item } } </script>
API
wepy.event
wepy.component
wepy.page
wepy.app
CHANGELOG
1.3.1 (2016-12-16)
-
新增對第三方Compiler的支持
-
新增pug編譯器
-
重新整理代碼結(jié)構(gòu),使用lerna維護不同的NPM包
-
重新處理Plugins,同樣交由第三方包處理
-
添加了編譯時檢測依賴的Compiler或者Plugins是否缺失的邏輯,如果缺失會自行安裝
如何制作小程序
-
添加了cli工具版本檢測的功能
1.1.9 (2016-12-14)
-
新增了wepy upgrade命令升級wepyjs版本
-
新增對第三方組件的支持
-
新增第三方組件[wepy-com-toast]
-
模板中添加toast組件測試
1.1.8 (2016-12-08)
-
修復了script使用src外鏈報錯的BUG
-
修復了LESS編譯會調(diào)用到SASS的BUG
-
優(yōu)化了事件傳參數(shù),支持直接傳參
-
加入了Travis-CI以及Coveralls
-
修復其它細節(jié)BUG問題
1.1.7 (2016-12-06)
-
script/template/style的屬性同時支持type和lang
-
添加mixins支持
1.1.6 (2016-12-02)
-
修復了組件ID大寫導致無法識別的問題
-
添加了對小程序頁面所有響應(yīng)事件的支持
-
修改wepy.config.js支持plugins
-
添加UglifyJsPlugin,在編譯時對生成的所有JS文件進行壓縮
-
添加ImageMinPlugin(不推薦使用,處理大圖片時還有問題)
-
添加wepy build --no-cache參數(shù),編譯時會重新編譯所有依賴文件
-
wepy new demo時,由在當前目錄下生成項目改為創(chuàng)建demo目錄,然后再生成項目
-
更新生成demo支持最新功能
1.1.4 (2016-12-01)
-
添加了小程序其它頁面事件的支持
-
修改默認配置文件.wepyrc為wepy.config.js,方便以后功能擴展。(兼容老配置文件
)
1.1.3 (2016-11-28)
-
修復SASS編譯異常導致watch結(jié)束的BUG
-
修復子組件修改時不會觸發(fā)父組件更新的BUG
-
修復$invoke('../')的BUG
-
修復頁面onLoad事件中傳參的BUG
1.1.1 (2016-11-23)
-
添加了對sass/scss的編譯支持
-
.wepyrc中加入對less/sass的配置支持
-
.wepyrc中添加wpyExt選項
-
更新生成模板
hishop微信小程序可以實現(xiàn)一鍵開通微信小程序,結(jié)合移動云商城,可以實現(xiàn)七大端口的線上和線下結(jié)合模式。
第二部分:如何開通一個小商店