本文長時間沒有更新 請跳轉小程序框架wepy文檔鏈接查看
項目創建與使用
以下安裝都通過npm安裝
- 安裝(更新) wepy 命令行工具。
npm install wepy-cli -g
- 在開發目錄生成開發DEMO。
wepy new myproject
- 切換至項目目錄。
cd myproject
- 開發實時編譯。
wepy build --watch
項目目錄結構
開發使用說明
- 使用微信開發者工具新建項目,本地開發選擇
dist
目錄。 - 微信開發者工具-->項目-->關閉ES6轉ES5。重要:漏掉此項會運行報錯。
- 微信開發者工具-->項目-->關閉上傳代碼時樣式自動補全 重要:某些情況下漏掉此項會也會運行報錯。
- 微信開發者工具-->項目-->關閉代碼壓縮上傳 重要:開啟后,會導致真機computed, props.sync 等等屬性失效。
- 本地項目根目錄運行wepy build --watch,開啟實時編譯。
主要解決問題
- 開發模式轉換
- 支持組件化開發
- 支持加載外部NPM包
- 單文件模式,使得目錄結構更加清晰
- 默認使用babel編譯,支持ES6/7的一些新特性。
- 針對原生API進行優化
下面對這幾點分別進行說明
1. 開發模式轉換
在原有的小程序的開發模式下進行再次封裝,更貼近于現有MVVM框架開發模式??蚣茉陂_發過程中參考了一些現在框架的一些特性,并且融入其中
官方DEMO代碼:
//index.js
//獲取應用實例
var app = getApp()
Page({
data: {
motto: 'Hello World',
userInfo: {}
},
//事件處理函數
bindViewTap: function() {
console.log('button clicked')
},
onLoad: function () {
console.log('onLoad')
}
})
基于wepy的實現:
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. 支持組件化開發
// index.wpy
<template>
<view>
<panel>
<h1 slot="title"></h1>
</panel>
<counter1 :num="myNum"></counter1>
<counter2 :num.sync="syncNum"></counter2>
<list :item="items"></list>
</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
};
data = {
myNum: 50,
syncNum: 100,
items: [1, 2, 3, 4]
}
}
</script>
3. 支持加載外部npm包
在編譯過程當中,會遞歸遍歷代碼中的require然后將對應依賴文件從node_modules當中拷貝出來,并且修改require為相對路徑,從而實現對外部NPM包的支持。如下圖:
4. 單文件模式,使得目錄結構更加清晰
官方目錄結構要求app必須有三個文件app.json,app.js,app.wxss,頁面有4個文件 index.json,index.js,index.wxml,index.wxss。而且文件必須同名。
官方DEMO:
使用wepy框架
project
└── src
├── coms
| └── child.wpy
├── pages
| ├── index.wpy index 頁面配置、結構、樣式、邏輯
| └── log.wpy log 頁面配置、結構、樣式、邏輯
└──app.wpy 小程序配置項(全局樣式配置、聲明鉤子等)
5. 默認使用babel編譯,支持ES6/7的一些新特性。
用戶可以通過修改wepy.config.js(老版本使用.wepyrc)配置文件,配置自己熟悉的babel環境進行開發。默認開啟使用了一些新的特性如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);
};
}
6. 針對原生API進行優化。
對現在API進行promise處理,同時修復一些現有API的缺陷,比如:wx.request并發問題等。 原有代碼:
onLoad = function () {
var self = this;
wx.login({
success: function (data) {
wx.getUserInfo({
success: function (userinfo) {
self.setData({userInfo: userinfo});
}
});
}
});
}
基于wepy實現代碼:
import wepy from 'wepy';
async onLoad() {
await wepy.login();
this.userInfo = await wepy.getUserInfo();
}
在同時并發10個request請求測試時:不使用wepy,請求會發生錯誤。解決辦法,使用wepy。
wpy文件說明
wpy
文件的編譯過程:
一個.wpy文件分為三部分
- 樣式
<style></style>
對應原有wxss
文件 - 模板
<template></template>
對應原有wxml
文件 - 代碼
<script></script>
對應原有js
文件,分為兩部分:1)邏輯部分,除了config對象之外的部分,對應于原生的.js文件;2)配置部分,即config對象,對應于原生的.json文件。
其中入口文件app.wpy
不需要template
,所以編譯時會被忽略。這三個標簽都支持lang
和src
屬性,lang
決定其代碼編譯過程,src
決定是否外聯代碼,存在src
屬性且有效時,忽略內斂代碼,示例如下:
<style lang="less" src="page1.less"></style>
<template lang="wxml" src="page1.wxml"></template>
<script>
// some code
</script>
標簽對應lang
值如下表所示:
標簽 | lang默認值 | lang支持值 |
---|---|---|
style | css | css,less,sass,style |
template | wxml | wxml,xml,pug(原jade) |
script | bable | bable,TypeScript |
script 說明
1. 程序入口 app.wpy
<style lang="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
屬性對應原有的app.json
,編譯時會根據config
生成app.json
文件,如果需要修改config
中的內容,請使用系統提供API。
2. 頁面 index.wpy
<style lang="less">
/** less **/
</style>
<template lang="wxml">
<view>
</view>
<counter1></counter1>
</template>
<script>
import wepy from '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
,主要屬性說明如下:
屬性 | 說明 |
---|---|
config | 頁面配置對象,對應于原生的page.json文件,類似于app.wpy中的config
|
components | 頁面組件列表對象,聲明頁面所引入的組件列表 |
data | 頁面渲染數據對象,存放可用于頁面模板綁定的渲染數據 |
methods | wmxl的事件捕捉,如bindtap ,bindchange
|
events | WePY組件事件處理函數對象,存放響應組件之間通過$broadcast 、$emit 、$invoke 所傳遞的事件的函數 |
其它 | 如onLoad ,onReady 等小程序事件以及其它自定義方法與屬性 |
3.組件 com.wpy
<style lang="less">
/** less **/
</style>
<template lang="wxml">
<view> </view>
</template>
<script>
import wepy from 'wepy';
export default class Com extends wepy.component {
components = {};
data = {};
methods = {};
events = {};
// Other properties
}
</script>
頁面入口繼承自wepy.component
,屬性與頁面屬性大致相同,除了不需要config
以及頁面特有的一些小程序事件等等。
實例
小程序在 WePY 中,被分為三個實例,App,Page,Component。其中Page實例繼承自Component。聲明方式如下:
import wepy from 'wepy';
// 聲明一個App文件
export default class MyAPP extends wepy.app {
}
// 聲明一個Page文件
export default class IndexPage extends wepy.page {
}
// 聲明一個組件文件
export default class MyComponent extends wepy.component {
}
App 實例
App小程序實例中主要包含小程序生命周期函數、config配置對象、globalData全局數據對象,以及其他自定義方法與屬性。
import wepy from 'wepy';
export default class MyAPP extends wepy.app {
customData = {};
customFunction () { }
onLaunch () {}
onShow () {}
config = {}; // 對應 app.json 文件
globalData = {}
}
在 Page 實例中,可以通過this.$parent
來訪問 App 實例。
Page 和 Component 實例
由于Page
頁面實際上繼承自Component
組件,即Page
也是組件。除擴展了頁面所特有的config
配置以及特有的頁面生命周期函數之外,其它屬性和方法與Component
一致,因此這里以Page
頁面為例進行介紹。
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 = {}; // 聲明組件之間的事件處理函數
}
methods
屬性只聲明頁面bind
,catch
事件,不能聲明自定義方法。
組件
小程序支持js模塊化,但彼此獨立,業務代碼與交互事件仍需在頁面處理。無法實現組件化的松耦合與復用的效果。
wepy讓小程序支持組件化開發,組件的所有業務與功能在組件本身實現,組件與組件之間彼此隔離,上述例子在wepy的組件化開發過程中,A組件只會影響到A綁定的myclick,B也如此。
普通組件引用
當頁面或者組件需要引入子組件時,需要在頁面或者 script
中的components
給組件分配唯一ID,并且在template
中添加<component>
標簽。
<template>
<child></child>
</template>
<script>
import wepy from 'wepy';
import Child from './coms/child';
export default class Index extends wepy.component {
components = {
child: Child
};
}
</script>
注意:
WePY中的組件都是靜態組件,是以組件ID作為唯一標識的,每一個ID都對應一個組件實例,當頁面引入兩個相同ID組件時,這兩個組件共用同一個實例與數據,當其中一個組件數據變化時,另外一個也會一起變化。 如果需要避免這個問題,則需要分配多個組件ID和實例。
<template>
<view class="child1">
<child></child>
</view>
<view class="child2">
<anotherchild></anotherchild>
</view>
</template>
<script>
import wepy from 'wepy';
import Child from './coms/child';
export default class Index extends wepy.component {
components = {
child: Child,
anotherchild: Child
};
}
</script>
<repeat>
的使用(循環列表組件引用)
當需要循環渲染WePY
組件時(類似于通過wx:for
循環渲染原生的wxml
標簽),必須使用WePY
定義的輔助標簽<repeat>
<template>
<repeat for="{{list}}" key="index" index="index" item="item">
<child :item="item"></child>
</repeat>
</template>
<script>
import wepy from 'wepy';
import Child from './coms/child';
export default class Index extends wepy.component {
components = {
child: Child
};
data = {
list: [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}]
}
}
</script>
computed
計算屬性
類型:
{ [key: string]: Function }
詳細:
- computed計算屬性,是一個有返回值的函數,可直接被當作綁定數據來使用。因此類似于data屬性,代碼中可通過this.計算屬性名來引用,模板中也可通過{{ 計算屬性名 }}來綁定數據。
- 只要是組件中有任何數據發生了改變,那么所有計算屬性就都會被重新計算。
data = {
a: 1
};
computed = {
aPlus () {
return this.a + 1;
}
}
watcher
監聽屬性更新
類型:
{ [key: string]: Function }
詳細:
通過監聽器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)
}
Props
傳值
- 靜態傳值
- 動態傳值
靜態傳值
使用靜態傳值時,子組件會接收到字符串的值。
<child title="mytitle"></child>
// child.wpy
props = {
title: String
};
onLoad () {
console.log(this.title); // mytitle
}
注意:靜態傳值只能傳遞String類型,不存在Number,Boolean等類型。
動態傳值
動態傳值是指父組件向子組件傳遞動態數據內容,父子組件數據完全獨立互不干擾。但可以通過使用.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: Number,
default: 50,
twoWay: true
}
};
onLoad () {
console.log(this.title); // p-title
console.log(this.syncTitle); // p-title
console.log(this.twoWayTitle); // 50
this.title = 'c-title';
console.log(this.$parent.parentTitle); // p-title.
this.twoWayTitle = 60;
console.log(this.$parent.parentTitle); // 60. --- twoWay為true時,子組件props修改會改變父組件對應的值
this.$parent.parentTitle = 'p-title-changed';
console.log(this.title); // 'p-title';
console.log(this.syncTitle); // 'p-title-changed' --- 有sync屬性的props,當父組件改變時,會影響子組件的值。
}
組件通信與交互
wepy.component
基類提供三個方法$broadcast
,$emit
,$invoke
,因此任一頁面或任一組件都可以調用這三種方法實現通信與交互。
組件的事件監聽需要寫在events
屬性下,如:
import wepy from 'wepy';
export default class Com extends wepy.component {
components = {};
data = {};
methods = {};
events = {
'some-event': (p1, p2, p3, $event) => {
console.log(`${this.name} receive ${$event.name} from ${$event.source.name}`);
}
};
// Other properties
}
$broadcast
$broadcast
事件是由父組件發起,所有子組件都會收到此廣播事件,除非事件被手動取消。事件廣播的順序為廣度優先搜索順序,如上圖,如果Page_Index
發起一個$broadcast
事件,那么接收到事件的先后順序為:A, B, C, D, E, F, G, H。如下圖:
$emit
$emit
與$broadcast
正好相反,事件發起組件的父組件會依次接收到$emit
事件,如上圖,如果E發起一個$emit
事件,那么接收到事件的先后順序為:A, Page_Index
。
$invoke
$invoke
是一個組件對另一個組件的直接調用,通過傳入的組件路徑找到相應組件,然后再調用其方法。 如果想在Page_Index
中調用組件A的某個方法
this.$invoke('ComA', 'someMethod', 'someArgs');
如果想在組件A中調用組件G的某個方法:
this.$invoke('./../ComB/ComG', 'someMethod', 'someArgs');
組件自定義事件
可以使用@customEvent.user
綁定用戶自定義組件事件。
其中,@
表示事件修飾符,customEvent
表示事件名稱,.user
表示事件后綴。
目前有三種后綴:
.default
: 綁定小程序冒泡事件事件,如bindtap
。
.stop
: 綁定小程序非冒泡事件,如catchtap
。
.user
: 綁定用戶自定義組件事件,通過``$emit`觸發。
// index.wpy
<template>
<child @childFn.user="parentFn"></child>
</template>
<script>
import wepy from 'wepy';
import Child from './coms/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>
組件內容分發slot
WePY中的slot插槽作為內容分發標簽的空間占位標簽,便于在父組件中通過對相當于擴展板卡的內容分發標簽的“插拔”,更為靈活、方便地對子組件進行內容分發。
具體使用方法是,首先在子組件template模板部分中聲明slot標簽作為內容插槽,同時必須在其name屬性中指定插槽名稱,還可設置默認的標簽內容;然后在引入了該帶有插槽的子組件的父組件template模板部分中聲明用于“插拔”的內容分發標簽。
注意,這些父組件中的內容分發標簽必須具有slot屬性,并且其值為子組件中對應的插槽名稱,這樣父組件內容分發標簽中的內容會覆蓋掉子組件對應插槽中的默認內容。
另外,要特別注意的是,父組件中一旦聲明了對應于子組件插槽的內容分發標簽,即便沒有內容,子組件插槽中的默認內容也不會顯示出來,只有刪除了父組件中對應的內容分發標簽,才能顯示出來。
在Panel組件中有以下模板:
<view class="panel">
<slot name="title">默認標題</slot>
<slot>
默認內容
</slot>
</view>
在父組件使用Pannel組件時,可以這樣使用:
<panel>
<view>
<text>這是我放到的內容</text>
</view>
<view slot="title">Panel的Title</view>
</panel>
wepy 數據綁定
- 小程序數據綁定
this.setData({title: 'this is title'});
- wepy 數據綁定
this.title = 'this is title';
wepy
使用臟數據檢查對setData
進行封裝,在函數運行周期結束時執行臟數據檢查,一來可以不用關心頁面多次setData
是否會有性能上的問題,二來可以更加簡潔去修改數據實現綁定,不用重復去寫setData
方法。
但需注意,在函數運行周期之外的函數里去修改數據需要手動調用$apply
方法。如:
setTimeout(() => {
this.title = 'this is title';
this.$apply();
}, 3000);
wx.request 接收參數
// 官方
wx.request({
url: 'xxx',
success: function (data) {
console.log(data);
}
});
// wepy 使用方式
wepy.request('xxxx').then((d) => console.log(d));
wepy 優化事件參數傳遞
官方使用方法
<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 建議傳參方式
<view data-wepy-params="{{index}}-wepy-otherparams" bindtap="tapName"> Click me! </view>
methods: {
tapName (id, title, other, event) {
console.log(id, title, other)// output: 1, wepy, otherparams
}
}
wepy 1.1.8以后的版本,只允許傳string。
<view bindtap="tapName({{index}}, 'wepy', 'otherparams')"> Click me! </view>
methods: {
tapName (id, title, other, event) {
console.log(id, title, other)// output: 1, wepy, otherparams
}
}
組件代替模板和模塊
官方
<!-- 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 '../components/item';
export default class Index extends wepy.page {
components = { Item }
}
</script>
API
-
wepy.app Class
App 基類,小程序入口。 -
wepy.component Class
組件基類 -
wepy.page Class
頁面類,繼承自wepy.component,擁有頁面所有的屬性與方法。 -
wepy.event Class
小程序事件封裝類 -
wepy.mixin Class
Mixin基類,用于復用不同組件中的相同功能。