簡介
微信小程序入門門檻低、開發週期短、代碼編寫靈活、傳播速度快等優點讓微信小程序迅速火爆,開發者紛紛涌入,任何語言開發者一旦多了,就會有新的框架出來,WePY就是一個優秀的微信小程序開發框架。它讓微信小程序的開發更加簡單,功能更加強大,並且也優化了文件結構,熟悉了WePY之後確實可以讓微信小程序開發更上一層樓。
在安裝使用WePY框架之前需要安裝npm,這部分就略過了,大多數開發者應該都是裝了npm的,下面就從WePY的安裝開始講起,列舉各項使用方法。
1、安裝WePY(WePY的安裝或更新都通過npm進行)
npm install -g wepy-cli
2、初始化項目
// 1.7.0之前 wepy new myproject // 1.7.0之後 wepy init standard myproject
3、查看WePY版本號
// 成功查看版本即表示工程初始化正確 wepy --version
4、安裝依賴
npm install
5、開啓實時編譯
npm run dev
或者(前者其實就是調用的後者)
wepy build --watch
6、project.config.json
{ "description": "A WePY project", "setting": { "urlCheck": true, "es6": false, "postcss": false, "minified": false, "newFeature": true }, "compileType": "miniprogram", "appid": "touristappid", "projectname": "hellowepy", "miniprogramRoot": "./dist", "condition": {} }
- es6: 對應關閉ES6轉ES5選項,關閉。 重要:未關閉會運行報錯。
- postcss: 對應關閉上傳代碼時樣式自動補全選項,關閉。 重要:某些情況下漏掉此項也會運行報錯。
- minified: 對應關閉代碼壓縮上傳選項,關閉。重要:開啓後,會導致真機computed, props.sync 等等屬性失效。(注:壓縮功能可使用WePY提供的build指令代替,詳見後文相關介紹以及Demo項目根目錄中的wepy.config.js和package.json文件。)
- urlCheck: 對應不檢查安全域名選項,開啓。 如果已配置好安全域名則建議關閉。
7、VS Code設置代碼高亮
文件後綴爲.wpy
,可共用Vue
的高亮規則,但需要手動設置。下面提供一些常見IDE或編輯器中實現代碼高亮的相關設置步驟以供參考(也可通過更改文件後綴名的方式來實現高亮,詳見後文相關介紹)。
- Sublime
1. 打開
Sublime->Preferences->Browse Packages..
進入用戶包文件夾。 2. 在此文件夾下打開cmd,運行git clone [email protected]:vuejs/vue-syntax-highlight.git
,無GIT用戶可以直接下載zip包解壓至當前文件夾。 3. 關閉.wpy
文件重新打開即可高亮。 - WebStorm/PhpStorm
1. 打開
Settings
,搜索Plugins
,搜索Vue.js
插件並安裝。 2. 打開Settings
,搜索File Types
,找到Vue.js Template
,在Registered Patterns
添加*.wpy
,即可高亮。 - Atom
1. 在Atom裏先安裝Vue的語法高亮 -
language-vue
,如果裝過了就忽略這一步。 2. 打開Atom -> Config
菜單。在core
鍵下添加:
customFileTypes: "text.html.vue": [ "wpy" ]
- VS Code
1. 在 Code 裏先安裝 Vue 的語法高亮插件
Vetur
。 2. 打開任意.wpy
文件。 3. 點擊右下角的選擇語言模式,默認爲純文本
。 4. 在彈出的窗口中選擇.wpy 的配置文件關聯...
。 5. 在選擇要與 .wpy 關聯的語言模式
中選擇Vue
。 6. 在VS Code編輯器設置中設置。 //文件-首選項-設置-settings.json settings.json "files.associations": { "*.wpy": "vue" }
8、代碼規範
- 變量與方法儘量使用駝峯式命名,並且注意避免使用
$
開頭。 以$
開頭的標識符爲WePY框架的內建屬性和方法,可在JavaScript腳本中以this.的方式直接使用。 - 小程序入口、頁面、組件文件名的後綴爲.wpy;外鏈的文件可以是其它後綴。
- 使用ES6語法開發。框架在ES6(ECMAScript 6)下開發,因此也需要使用ES6開發小程序,ES6中有大量的語法糖可以讓我們的代碼更加簡潔高效。
- 使用Promise。框架默認對小程序提供的API全都進行了Promise處理,甚至可以直接使用async/await等新特性進行開發。啓用Promise方法。
- 事件綁定語法使用優化語法代替。 a.原bindtap="click"替換爲@tap="click",原catchtap="click"替換爲@tap.stop="click"。原capture-bind:tap="click"替換爲@tap.capture="click", b.原capture-catch:tap="click"替換爲@tap.capture.stop="click"。
- 事件傳參使用優化後語法代替。原bindtap="click" data-index={{index}}替換爲@tap="click({{index}})"。
- 自定義組件命名應避開微信原生組件名稱以及功能標籤<repeat>。不可以使用input、button、view、repeat等微信小程序原生組件名稱命名自定義組件;另外也不要使用WePY框架定義的輔助標籤repeat命名。
9、wepy.config.js配置文件說明
- wpyExt:缺省值爲'.wpy',IDE默認情況下不會對此文件類型進行高亮顯示,這種情況下,除了前問代碼高亮部分的介紹進行設置外,還可以直接將相關文件的後綴名由.wpy改爲.vue。
- compilers:compilers爲1.3.1版本之後的功能,如果需要使用其它語法,請先配置compilers,然後再安裝相應的compilers。目前支持wepy-compiler-less, wepy-compiler-postcss,wepy-compiler-sass、wepy-compiler-babel、wepy-compiler-pug,其他compiler持續開發中......
- 對應各compiler請參考各自文檔: sass:sass編譯配置,參見這裏。https://github.com/sass/node-sass less:less編譯配置,參見這裏。http://lesscss.org/#using-less-usage-in-code postcss:postcss編譯配置,參見這裏。http://www.css88.com/archives/7317 stylus:stylus編譯配置,參見這裏。http://www.zhangxinxu.com/jq/stylus/js.php babel:babel編譯配置,參見這裏。http://babeljs.io/docs/usage/options/ typescript:typescript編譯配置,參見這裏。https://www.tslang.cn/docs/home.html
- plugins:plugins爲1.1.6版本之後的功能,目前支持js壓縮wepy-plugin-ugliyjs、圖片壓縮wepy-plugin-imagemin,其他plugin持續開發中...
10、.wpy文件
- 一個.wpy文件可分爲三大部分,各自對應於一個標籤:
- 腳本部分,即<script></script>標籤中的內容,又可分爲兩個部分: 邏輯部分,除了config對象之外的部分,對應於原生的.js文件; 配置部分,即config對象,對應於原生的.json文件。
- 結構部分,即<template></template>模板部分,對應於原生的.wxml文件。
- 樣式部分,即<style></style>樣式部分,對應於原生的.wxss文件。
其中,小程序入口文件app.wpy不需要template,所以編譯時會被忽略。.wpy文件中的script、template、style這三個標籤都支持lang和src屬性,lang決定了其代碼編譯過程,src決定是否外聯代碼,存在src屬性且有效時,會忽略內聯代碼。
11、小程序入口app.wpy
入口文件app.wpy中所聲明的小程序實例繼承自wepy.app類,包含一個config屬性和其它全局屬性、方法、事件。其中config屬性對應原生的app.json文件,build編譯時會根據config屬性自動生成app.json文件,如果需要修改config中的內容,請使用微信提供的相關API。
12、頁面page.wpy
頁面文件page.wpy中所聲明的頁面實例繼承自wepy.page類,該類的主要屬性介紹如下:
- config:頁面配置對象,對應於原生的page.json文件,類似於app.wpy中的config
- components:頁面組件列表對象,聲明頁面所引入的組件列表
- data:頁面渲染數據對象,存放可用於頁面模板綁定的渲染數據
- methods:wxml事件處理函數對象,存放響應wxml中所捕獲到的事件的函數,如bindtap、bindchange
- events:WePY組件事件處理函數對象,存放響應組件之間通過
emit、$invoke所傳遞的事件的函數
- 其它:小程序頁面生命週期函數,如onLoad、onReady等,以及其它自定義的方法與屬性
13、app小程序實例
import wepy from 'wepy'; export default class MyAPP extends wepy.app { customData = {}; customFunction () {} onLaunch () {} onShow () {} config = {} // 對應 app.json 文件 globalData = {} }
14、page頁面實例和Component組件實例
import wepy from 'wepy'; export default class MyPage extends wepy.page { //export default class MyComponent extends wepy.component { customData = {} //自定義數據 customFunction () {} //自定義方法 onLoad () {} //在Page和Component共用的生命週期函數 onShow () {} //只在Page中存在的頁面生命週期函數 config = {}; //只在Page實例中存在的配置數據,對應於原生的page.json文件 data = {}; //頁面所需數據均需在這裏聲明,可用於模板數據綁定 components = {}; //聲明頁面中所引用的組件,或聲明組件中所引用的子組件 mixins = []; //聲明頁面所引用的Mixin實例 computed = {}; //聲明計算屬性 watch = {}; //聲明數據watcher methods = {}; //聲明頁面wxml中標籤的事件處理函數。注意,此處只用於聲明頁面wxml中標籤的bind、catch事件,自定義方法需以自定義方法的方式聲明 events = {}; //聲明組件之間的事件處理函數 }
15、組件
原生小程序支持js模塊化,但彼此獨立,業務代碼與交互事件仍需在頁面處理。無法實現組件化的鬆耦合與複用的效果。
例如模板A中綁定一個bindtap="myclick",模板B中同樣綁定一樣bindtap="myclick",那麼就會影響同一個頁面事件。對於數據同樣如此。因此,只有通過改變變量或者事件方法,或者給其加不同前綴才能實現綁定不同事件或者不同數據。當頁面複雜之後就十分不利於開發維護。
因此,在WePY中實現了小程序的組件化開發,組件的所有業務與功能在組件本身實現,組件與組件之間彼此隔離,上述例子在WePY的組件化開發過程中,A組件只會影響到A所綁定的myclick,B也如此。
16、普通組件引用
當頁面需要引入組件或組件需要引入子組件時,必須在.wpy文件的<script>腳本部分先import組件文件,然後在components對象中給組件聲明唯一的組件ID,接着在<template>模板部分中添加以components對象中所聲明的組件ID進行命名的自定義標籤以插入組件。
/** project └── src ├── components | └── child.wpy ├── pages | ├── index.wpy index 頁面配置、結構、樣式、邏輯 | └── log.wpy log 頁面配置、結構、樣式、邏輯 └──app.wpy 小程序配置項(全局公共配置、公共樣式、聲明鉤子等) **/ // index.wpy <template> <!-- 以`<script>`腳本部分中所聲明的組件ID爲名命名自定義標籤,從而在`<template>`模板部分中插入組件 --> <child></child> </template> <script> import wepy from 'wepy'; //引入組件文件 import Child from '../components/child'; export default class Index extends wepy.component { //聲明組件,分配組件id爲child components = { child: Child }; } </script>
17、組件的循環渲染
當需要循環渲染WePY組件時(類似於通過wx:for循環渲染原生的wxml標籤),必須使用WePY定義的輔助標籤<repeat>
/** project └── src ├── components | └── child.wpy ├── pages | ├── index.wpy index 頁面配置、結構、樣式、邏輯 | └── log.wpy log 頁面配置、結構、樣式、邏輯 └──app.wpy 小程序配置項(全局樣式配置、聲明鉤子等) **/ // index.wpy <template> <!-- 注意,使用for屬性,而不是使用wx:for屬性 --> <repeat for="{{list}}" key="index" index="index" item="item"> <!-- 插入<script>腳本部分所聲明的child組件,同時傳入item --> <child :item="item"></child> </repeat> </template> <script> import wepy from 'wepy'; // 引入child組件文件 import Child from '../components/child'; export default class Index extends wepy.component { components = { // 聲明頁面中要使用到的Child組件的ID爲child child: Child } data = { list: [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}] } } </script>
18、computed 計算屬性
computed計算屬性,是一個有返回值的函數,可直接被當作綁定數據來使用。因此類似於data屬性,代碼中可通過this.計算屬性名來引用,模板中也可通過{{ 計算屬性名 }}來綁定數據。
需要注意的是,只要是組件中有任何數據發生了改變,那麼所有計算屬性就都會被重新計算。
data = { a: 1 } //計算屬性aPlus,在腳本中可通過this.aPlus來引用,在模板中可通過{{ aPlus }}來插值 computed = { aPlus () { return this.a + 1 } }
19、watcher 監聽器
通過監聽器watcher能夠監聽到任何屬性的更新。監聽器在watch對象中聲明,類型爲函數,函數名與需要被監聽的data對象中的屬性同名,每當被監聽的屬性改變一次,監聽器函數就會被自動調用執行一次。
監聽器適用於當屬性改變時需要進行某些額外處理的情形。
data = { num: 1 } //監聽器函數名必須跟需要被監聽的data對象中的屬性num同名, //其參數中的newValue爲屬性改變後的新值,oldValue爲改變前的舊值 watch = { num (newValue, oldValue) { console.log(`num value: ${oldValue} -> ${newValue}`) } } //每當被監聽的屬性num改變一次,對應的同名監聽器函數num()就被自動調用執行一次 onLoad () { setInterval(() => { this.num++; this.$apply(); }, 1000) }
20、props 傳值
動態傳值是指父組件向子組件傳遞動態數據內容,父子組件數據完全獨立互不干擾。但可以通過使用.sync修飾符來達到父組件數據綁定至子組件的效果,也可以通過設置子組件props的twoWay: true來達到子組件數據綁定至父組件的效果。那如果既使用.sync修飾符,同時子組件props中添加的twoWay: true時,就可以實現數據的雙向綁定了。
注意:下文示例中的twoWay爲true時,表示子組件向父組件單向動態傳值,而twoWay爲false(默認值,可不寫)時,則表示子組件不向父組件傳值。這是與Vue不一致的地方,而這裏之所以仍然使用twoWay,只是爲了儘可能保持與Vue在標識符命名上的一致性。
在父組件template模板部分所插入的子組件標籤中,使用:prop屬性(等價於Vue中的v-bind:prop屬性)來進行動態傳值。
// parent.wpy <child :title="parentTitle" :syncTitle.sync="parentTitle" :twoWayTitle="parentTitle"></child> data = { parentTitle: 'p-title' }; // child.wpy props = { // 靜態傳值 title: String, // 父向子單向動態傳值 syncTitle: { type: String, default: 'null' }, twoWayTitle: { type: String, default: 'nothing', twoWay: true } }; onLoad () { console.log(this.title); // p-title console.log(this.syncTitle); // p-title console.log(this.twoWayTitle); // p-title this.title = 'c-title'; console.log(this.$parent.parentTitle); // p-title. this.twoWayTitle = 'two-way-title'; this.$apply(); console.log(this.$parent.parentTitle); // two-way-title. --- twoWay爲true時,子組件props中的屬性值改變時,會同時改變父組件對應的值 this.$parent.parentTitle = 'p-title-changed'; this.$parent.$apply(); console.log(this.title); // 'c-title'; console.log(this.syncTitle); // 'p-title-changed' --- 有.sync修飾符的props屬性值,當在父組件中改變時,會同時改變子組件對應的值。 }
21、組件通信與交互
wepy.component基類提供
emit、$invoke三個方法用於組件之間的通信和交互,如:
this.$emit('some-event', 1, 2, 3, 4);
用於監聽組件之間的通信與交互事件的事件處理函數需要寫在組件和頁面的events對象中,如:
import wepy from 'wepy' export default class Com extends wepy.component { components = {}; data = {}; methods = {}; // events對象中所聲明的函數爲用於監聽組件之間的通信與交互事件的事件處理函數 events = { 'some-event': (p1, p2, p3, $event) => { console.log(`${this.$name} receive ${$event.name} from ${$event.source.$name}`); } }; // Other properties }
- $broadcast 由父組件發起,所有子組件都會收到此廣播事件,除非事件被手動取消。事件廣播的順序爲廣度優先搜索順序
- **
broadcast正好相反,事件發起組件的所有祖先組件會依次接收到$emit事件。
- $invoke 一個頁面或組件對另一個組件中的方法的直接調用,通過傳入組件路徑找到相應的組件,然後再調用其方法。
Page_Index中調用組件ComA的某個方法
this.$invoke('ComA', 'someMethod', 'someArgs');
組件ComA中調用組件ComG的某個方法
this.$invoke('./../ComB/ComG', 'someMethod', 'someArgs');
22、組件自定義事件處理函數
可以通過使用.user修飾符爲自定義組件綁定事件,如:@customEvent.user="myFn",其中,@表示事件修飾符,customEvent 表示事件名稱,.user表示事件後綴。目前總共有三種事件後綴:
- default: 綁定小程序冒泡型事件,如bindtap,.default後綴可省略不寫;
- stop: 綁定小程序捕獲型事件,如catchtap;
- user: 綁定用戶自定義組件事件,通過$emit觸發。注意,如果用了自定義事件,則events中對應的監聽函數不會再執行。
// index.wpy <template> <child @childFn.user="parentFn"></child> </template> <script> import wepy from 'wepy' import Child from '../components/child' export default class Index extends wepy.page { components = { child: Child } methods = { parentFn (num, evt) { console.log('parent received emit event, number is: ' + num) } } } </script> // child.wpy <template> <view @tap="tap">Click me</view> </template> <script> import wepy from 'wepy' export default class Child extends wepy.component { methods = { tap () { console.log('child is clicked') this.$emit('childFn', 100) } } } </script>
23、slot 組件插槽
子組件template模板部分中聲明slot標籤作爲內容插槽,同時必須在其name屬性中指定插槽名稱,還可設置默認的標籤內容;然後在引入了該帶有插槽的子組件的父組件template模板部分中聲明用於“插拔”的內容分發標籤。
注意,這些父組件中的內容分發標籤必須具有slot屬性,並且其值爲子組件中對應的插槽名稱,這樣父組件內容分發標籤中的內容會覆蓋掉子組件對應插槽中的默認內容。
另外,要特別注意的是,父組件中一旦聲明瞭對應於子組件插槽的內容分發標籤,即便沒有內容,子組件插槽中的默認內容也不會顯示出來,只有刪除了父組件中對應的內容分發標籤,才能顯示出來。
<view class="panel"> <slot name="title">默認標題</slot> <slot name="content">默認內容</slot> </view> <panel> <view slot="title">新的標題</view> <view slot="content"> <text>新的內容</text> </view> </panel>
24、第三方組件
略
25、WXS (WeiXin Script)
wxs是基於原生的wxs去實現的,只是通過編譯把現在的語法編譯爲原生語法。 wxs必須是外鏈文件。並且後綴爲.wxs。 wxs引入後只能在template中使用,不能在script中使用。
// mywxs.wxs module.exports = { text: 'This is from wxs', filter: function (num) { return num.toFixed(2); } }; // index.wpy <template> <text>{{m1.text}}</text> <text>{{m1.filter(num)}}</text> </template> <script> import wepy from 'wepy'; import mywxs from '../wxs/mywxs.wxs'; export default class Index extends wepy.page { data = { num: 10 }; wxs = { m1: mywxs } }; </script>
26、interceptor 攔截器
可以使用WePY提供的全局攔截器對原生API的請求進行攔截。
import wepy from 'wepy'; export default class extends wepy.app { constructor () { // this is not allowed before super() super(); // 攔截request請求 this.intercept('request', { // 發出請求時的回調函數 config (p) { // 對所有request請求中的OBJECT參數對象統一附加時間戳屬性 p.timestamp = +new Date(); console.log('config request: ', p); // 必須返回OBJECT參數對象,否則無法發送請求到服務端 return p; }, // 請求成功後的回調函數 success (p) { // 可以在這裏對收到的響應數據對象進行加工處理 console.log('request success: ', p); // 必須返回響應數據對象,否則後續無法對響應數據進行處理 return p; }, //請求失敗後的回調函數 fail (p) { console.log('request fail: ', p); // 必須返回響應數據對象,否則後續無法對響應數據進行處理 return p; }, // 請求完成時的回調函數(請求成功或失敗都會被執行) complete (p) { console.log('request complete: ', p); } }); } }
27、數據綁定更新
WePY使用髒數據檢查對setData進行封裝,在函數運行週期結束時執行髒數據檢查,一來可以不用關心頁面多次setData是否會有性能上的問題,二來可以更加簡潔去修改數據實現綁定,不用重複去寫setData方法。
// 同步函數: this.title = 'this is new title'; // 異步函數中: setTimeout(() => { this.title = 'this is new title'; this.$apply(); }, 3000);
28、其它優化細節
- wx.request接收參數修改
// 原生代碼: wx.request({ url: 'xxx', success: function (data) { console.log(data); } }); // WePY使用方式, 需要開啓Promise支持 wepy.request('xxxx').then((d) => console.log(d)); // async/await的使用方式, 需要開啓Promise和async/await支持 async function request () { let d = await wepy.request('xxxxx'); console.log(d); }
- 優化事件參數傳遞
// 原生的事件傳參方式: <view data-id="{{index}}" data-title="wepy" data-other="otherparams" bindtap="tapName"> Click me! </view> Page({ tapName: function (event) { console.log(event.currentTarget.dataset.id)// output: 1 console.log(event.currentTarget.dataset.title)// output: wepy console.log(event.currentTarget.dataset.other)// output: otherparams } }); // WePY 1.1.8以後的版本,只允許傳string。 <view @tap="tapName({{index}}, 'wepy', 'otherparams')"> Click me! </view> methods: { tapName (id, title, other, event) { console.log(id, title, other)// output: 1, wepy, otherparams } }
29、存在的問題
WePY 1.x 版本中,組件使用的是靜態編譯組件,即組件是在編譯階段編譯進頁面的,每個組件都是唯一的一個實例,目前只提供簡單的 repeat 支持。不支持在 repeat 的組件中去使用 props, computed, watch 等等特性。
錯誤使用 :
// list.wpy <view>{{test.name}}</view> // index.wpy <repeat for="{{mylist}}"> <List :test.sync="item"></List> </repeat> <!-- 推薦用法 ---> // list.wpy <repeat for="{{mylist}}"> <view>{{item.name}}</view> </repeat> // index.wpy <List :mylist.sync="mylist"></List>