学习途中总是忘心大,看了官方文档,自己也总结写一遍吧,写一遍记得印象更深刻~
目录
1、wepy的安装与使用
2、vscode 使用 wepy的小技巧
3、微信开发者工具使用
4、项目配置
5、功能特性
6、进阶学习
复制代码
一、wepy的安装与使用
全局安装或更新WePY命令行工具
npm install wepy-cli -g
复制代码
初始化项目
wepy init standard test_wepy // 标准模式,包含了react等测试
wepy init empty test_wepy // 空白模式,适合初始化的空项目
复制代码
安装依赖包
yarn install / npm install
复制代码
项目生成后是这样的
// 以下是空白模式下的目录结构
├── dist 小程序运行代码目录(该目录由WePY的build指令自动编译生成,请不要直接修改该目录下的文件)
├── node_modules
├── src 代码编写的目录(该目录为使用WePY后的开发目录)
| ├── components WePY组件目录(组件不属于完整页面,仅供完整页面或其他组件引用)
| | ├── com_a.wpy 可复用的WePY组件a
| | └── com_b.wpy 可复用的WePY组件b
| ├── pages WePY页面目录(属于完整页面)
| | ├── index.wpy index页面(经build后,会在dist目录下的pages目录生成index.js、index.json、index.wxml和index.wxss文件)
| | └── other.wpy other页面(经build后,会在dist目录下的pages目录生成other.js、other.json、other.wxml和other.wxss文件)
| └── app.wpy 小程序配置项(全局数据、样式、声明钩子等;经build后,会在dist目录下生成app.js、app.json和app.wxss文件)
└── package.json 项目的package配置
复制代码
开启实时编译功能
npm run dev
复制代码
至此我们的第一个wepy小程序项目构建完成。
二、vscode 使用 wepy的小技巧
首选项-应用程序-setting.json加上如下代码,重启就ok
"files.associations": {
"*.vue": "vue",
"*.wpy": "vue",
"*.wxml": "html",
"*.wxss": "css"
},
"emmet.syntaxProfiles": {
"vue-html": "html",
"vue": "html"
}
复制代码关于wepy的一些插件
三、微信开发者工具使用
注意事项
1.使用微信开发者工具-->添加项目,项目目录请选择dist目录。
2.微信开发者工具-->项目-->关闭ES6转ES5。 【重要】:漏掉此项会运行报错。
3.微信开发者工具-->项目-->关闭上传代码时样式自动补全。 【重要】:某些情况下漏掉此项也会运行报错。
4.微信开发者工具-->项目-->关闭代码压缩上传。 【重要】:开启后,会导致真机computed, props.sync 等等属性失效。
复制代码
四、项目配置
1.7.0之后的版本init新生成的代码包会在根目录包含project.config.json文件,之前生成的代码包可能不存在project.config.json文件。
{
"description": "A WePY project",
"setting": {
"urlCheck": true, // 对应不检查安全域名选项,开启。 如果已配置好安全域名则建议关闭。
"es6": false, // 对应关闭ES6转ES5选项,关闭。 重要:未关闭会运行报错。
"postcss": false, // 对应关闭上传代码时样式自动补全选项,关闭。 重要:某些情况下漏掉此项也会运行报错。
"minified": false // 对应关闭代码压缩上传选项,关闭。重要:开启后,会导致真机computed, props.sync 等等属性失效。
},
"compileType": "miniprogram",
"appid": "touristappid",
"projectname": "empty_wepy",
"miniprogramRoot": "./dist" // 自己创建一个wepy项目,竟然不用在dist目录下也可以在开发者工具中打开,原因在此
}
复制代码
五、功能特性
1、单文件模式
原生小程序要求app实例必须有3个文件:app.js、app.json、app.wxss,而page页面则一般有4个文件:page.js、page.json、page.wxml、page.wxss,并且还要求app实例的3个文件以及page页面的4个文件除后缀名外必须同名
而在WePY中则使用了单文件模式,将原生小程序app实例的3个文件统一为app.wpy,page页面的4个文件统一为page.wpy。使用WePY开发前后的开发目录结构对比如下:
// 原生小程序的目录结构:
project
├── pages
| ├── index
| | ├── index.js index 页面逻辑
| | ├── index.json index 页面配置
| | ├── index.wxml index 页面结构
| | └── index.wxss index 页面样式
| └── log
| ├── log.js log 页面逻辑
| ├── log.json log 页面配置
| ├── log.wxml log 页面结构
| └── log.wxss log 页面样式
├── app.js 小程序逻辑
├── app.json 小程序公共配置
└── app.wxss 小程序公共样式
复制代码// wepy小程序
project
└── src
├── pages
| ├── index.wpy index 页面逻辑、配置、结构、样式
| └── log.wpy log 页面逻辑、配置、结构、样式
└──app.wpy 小程序逻辑、公共配置、公共样式
复制代码
2、默认使用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);
};
}
复制代码
3、针对原生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后:
4、开发模式转换
WePY框架在开发过程中参考了Vue等现有框架的一些语法风格和功能特性,对原生小程序的开发模式进行了再次封装,更贴近于MVVM架构模式。
//index.wpy中的
import wepy from 'wepy';
//通过继承自wepy.page的类创建页面逻辑
export default class Index extends wepy.page {
//可用于页面模板绑定的数据
data = {
motto: 'Hello World',
userInfo: {}
};
//事件处理函数(集中保存在methods对象中)
methods = {
bindViewTap () {
console.log('button clicked');
}
};
//页面的生命周期函数
onLoad() {
console.log('onLoad');
};
}
复制代码
5、支持组件化开发
具体详见下面。
六、进阶学习
1、wepy.config.js配置文件说明
执行wepy init standard demo后,会生成类似下面这样的配置文件。
const path = require('path');
var prod = process.env.NODE_ENV === 'production';
module.exports = {
wpyExt: '.wpy',
eslint: true,
cliLogs: !prod,
build: {
web: {
htmlTemplate: path.join('src', 'index.template.html'),
htmlOutput: path.join('web', 'index.html'),
jsOutput: path.join('web', 'index.js')
}
},
resolve: {
alias: {
counter: path.join(__dirname, 'src/components/counter'),
'@': path.join(__dirname, 'src')
},
aliasFields: ['wepy', 'weapp'],
modules: ['node_modules']
},
compilers: {
less: {
compress: prod
},
/*sass: {
outputStyle: 'compressed'
},*/
babel: {
sourceMap: true,
presets: [
'env'
],
plugins: [
'transform-class-properties',
'transform-decorators-legacy',
'transform-object-rest-spread',
'transform-export-extensions',
]
}
},
plugins: {
},
appConfig: {
noPromiseAPI: ['createSelectorQuery']
}
}
if (prod) {
// 压缩sass
// module.exports.compilers['sass'] = {outputStyle: 'compressed'}
// 压缩js
module.exports.plugins = {
uglifyjs: {
filter: /\.js$/,
config: {
}
},
// 图片压缩
imagemin: {
filter: /\.(jpg|png|jpeg)$/,
config: {
jpg: {
quality: 80
},
png: {
quality: 80
}
}
}
}
}
复制代码
2、.wpy文件说明
.wpy文件的编译过程过下:
一个.wpy文件可分为三大部分,各自对应于一个标签:
脚本部分: 即标签中的内容,又可分为两个部分:
逻辑部分: 除了config对象之外的部分,对应于原生的.js文件;
配置部分: 即config对象,对应于原生的.json文件。
结构部分: 即模板部分,对应于原生的.wxml文件。
样式部分: 即样式部分,对应于原生的.wxss文件。
复制代码
其中,小程序入口文件app.wpy不需要template,所以编译时会被忽略。.wpy文件中的script、template、style这三个标签都支持lang和src属性,lang决定了其代码编译过程,src决定是否外联代码,存在src属性且有效时,会忽略内联代码。
// some code
复制代码标签
lang默认值
lang支持值
style
css
css、less、scss、stylus、postcss
template
wxml
wxml、xml、pug(原jade)
script
babel
babel、TypeScript
3、脚本解释
小程序入口app.wpy
import wepy from 'wepy'
export default class extends wepy.app {
// 全局配置
config = {
pages: [
'pages/index', // 首页
'pages/find', // 发现页
'pages/my' // 个人中心
],
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#fff',
navigationBarTitleText: 'meils',
navigationBarTextStyle: 'black'
}
}
onLaunch() {
console.log('on launch')
}
}
复制代码
入口文件app.wpy中所声明的小程序实例继承自wepy.app类,包含一个config属性和其它全局属性、方法、事件。其中config属性对应原生的app.json文件,build编译时会根据config属性自动生成app.json文件,如果需要修改config中的内容,请使用微信提供的相关API。
页面page.wpy
import wepy from 'wepy';
import Counter from '../components/counter'; // 组件
export default class Page extends wepy.page {
config = {};
components = {counter1: Counter};
data = {};
methods = {};
events = {};
onLoad() {};
// Other properties
}
/** less **/
复制代码页面文件page.wpy中所声明的页面实例继承自wepy.page类,该类的主要属性介绍如下:
属性
说明
config
页面配置对象,对应于原生的page.json文件,类似于app.wpy中的config
components
页面组件列表对象,声明页面所引入的组件列表
data
页面渲染数据对象,存放可用于页面模板绑定的渲染数据
methods
wxml事件处理函数对象,存放响应wxml中所捕获到的事件的函数,如bindtap、bindchange
events
WePY组件事件处理函数对象,存放响应组件之间通过 $broadcast、$emit、$invoke所传递的事件的函数
其它
小程序页面生命周期函数,如onLoad、onReady等,以及其它自定义的方法与属性
组件com.wpy
import wepy from 'wepy';
export default class Com extends wepy.component {
components = {};
data = {};
methods = {};
events = {};
// Other properties
}
/** less **/
复制代码
组件文件com.wpy中所声明的组件实例继承自wepy.component类,除了不需要config配置以及页面特有的一些生命周期函数之外,其属性与页面属性大致相同。
4、实例
在 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 {
}
// 声明一个Component组件实例
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组件实例
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 = {}; // 声明组件之间的事件处理函数
}
复制代码这里需要特别强调一下:WePY中的methods属性只能声明页面wxml标签的bind、catch事件,不能声明自定义方法,这与Vue中的用法是不一致的。
5、组件
原生小程序支持js模块化,但彼此独立,业务代码与交互事件仍需在页面处理。无法实现组件化的松耦合与复用的效果。
例如模板A中绑定一个bindtap="myclick",模板B中同样绑定一样bindtap="myclick",那么就会影响同一个页面事件。对于数据同样如此。因此,只有通过改变变量或者事件方法,或者给其加不同前缀才能实现绑定不同事件或者不同数据。当页面复杂之后就十分不利于开发维护。
因此,在WePY中实现了小程序的组件化开发,组件的所有业务与功能在组件本身实现,组件与组件之间彼此隔离,上述例子在WePY的组件化开发过程中,A组件只会影响到A所绑定的myclick,B也如此。
普通组件引用
/**
project
└── src
├── components
| └── child.wpy
├── pages
| ├── index.wpy index 页面配置、结构、样式、逻辑
| └── log.wpy log 页面配置、结构、样式、逻辑
└──app.wpy 小程序配置项(全局公共配置、公共样式、声明钩子等)
**/
// index.wpy
import wepy from 'wepy';
//引入组件文件
import Child from '../components/child';
export default class Index extends wepy.page {
//声明组件,分配组件id为child
components = {
child: Child
};
}
复制代码需要注意的是,WePY中的组件都是静态组件,是以组件ID作为唯一标识的,每一个ID都对应一个组件实例,当页面引入两个相同ID的组件时,这两个组件共用同一个实例与数据,当其中一个组件数据变化时,另外一个也会一起变化。
如果需要避免这个问题,则需要分配多个组件ID和实例。代码如下:
import wepy from 'wepy';
import Child from '../components/child';
export default class Index extends wepy.page {
components = {
//为两个相同组件的不同实例分配不同的组件ID,从而避免数据同步变化的问题
child: Child,
anotherchild: Child
};
}
复制代码
注意:WePY中,在父组件template模板部分插入驼峰式命名的子组件标签时,不能将驼峰式命名转换成短横杆式命名(比如将childCom转换成child-com),这与Vue中的习惯是不一致。
组件的循环渲染
当需要循环渲染WePY组件时(类似于通过wx:for循环渲染原生的wxml标签),必须使用WePY定义的辅助标签,代码如下:
/**
project
└── src
├── components
| └── child.wpy
├── pages
| ├── index.wpy index 页面配置、结构、样式、逻辑
| └── log.wpy log 页面配置、结构、样式、逻辑
└──app.wpy 小程序配置项(全局样式配置、声明钩子等)
**/
// index.wpy
import wepy from 'wepy';
// 引入child组件文件
import Child from '../components/child';
export default class Index extends wepy.page {
components = {
// 声明页面中要使用到的Child组件的ID为child
child: Child
}
data = {
list: [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}]
}
}
复制代码
5、computed
computed计算属性,是一个有返回值的函数,可直接被当作绑定数据来使用。因此类似于data属性,代码中可通过this.计算属性名来引用,模板中也可通过{{ 计算属性名 }}来绑定数据。
需要注意的是,只要是组件中有任何数据发生了改变,那么所有计算属性就都会被重新计算。
data = {
a: 1
}
// 计算属性aPlus,在脚本中可通过this.aPlus来引用,在模板中可通过{{ aPlus }}来插值
computed = {
aPlus () {
return this.a + 1
}
}
复制代码
6、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)
}
复制代码
7、props 传值
props传值在WePY中属于父子组件之间传值的一种机制,包括静态传值与动态传值。
静态传值
静态传值为父组件向子组件传递常量数据,因此只能传递String字符串类型。
// child.wpy
props = {
title: String
};
onLoad () {
console.log(this.title); // mytitle
}
复制代码动态传值
通过使用.sync修饰符来达到父组件数据绑定至子组件的效果,也可以通过设置子组件props的twoWay: true来达到子组件数据绑定至父组件的效果。那如果既使用.sync修饰符,同时子组件props中添加的twoWay: true时,就可以实现数据的双向绑定了。
// parent.wpy
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属性值,当在父组件中改变时,会同时改变子组件对应的值。
}
复制代码
8、组件通信与交互
wepy.component基类提供$broadcast、$emit、$invoke三个方法用于组件之间的通信和交互,如:
$broadcast
$broadcast事件是由父组件发起,所有子组件都会收到此广播事件,除非事件被手动取消。事件广播的顺序为广度优先搜索顺序,如上图,如果页面Page_Index发起一个$broadcast事件,那么按先后顺序依次接收到该事件的组件为:ComA、ComB、ComC、ComD、ComE、ComF、ComG、ComH。如下图:
$emit
$emit与$broadcast正好相反,事件发起组件的所有祖先组件会依次接收到$emit事件。如果组件ComE发起一个$emit事件,那么接收到事件的先后顺序为:组件ComA、页面Page_Index。如下图:
$invoke
$invoke是一个页面或组件对另一个组件中的方法的直接调用,通过传入组件路径找到相应的组件,然后再调用其方法。
比如,想在页面Page_Index中调用组件ComA的某个方法:
this.$invoke('ComA', 'someMethod', 'someArgs');
复制代码
如果想在组件ComA中调用组件ComG的某个方法:
this.$invoke('./../ComB/ComG', 'someMethod', 'someArgs');
复制代码
9、组件自定义事件处理函数
可以通过使用.user修饰符为自定义组件绑定事件,如:@customEvent.user="myFn"
其中,@表示事件修饰符,customEvent 表示事件名称,.user表示事件后缀。
目前总共有三种事件后缀:
.default: 绑定小程序冒泡型事件,如bindtap,.default后缀可省略不写;
.stop: 绑定小程序捕获型事件,如catchtap;
.user: 绑定用户自定义组件事件,通过$emit触发。注意,如果用了自定义事件,则events中对应的监听函数不会再执行。
// index.wpy
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)
}
}
}
// child.wpy
Click me
import wepy from 'wepy'
export default class Child extends wepy.component {
methods = {
tap () {
console.log('child is clicked')
this.$emit('childFn', 100)
}
}
}
复制代码
10、slot 组件内容分发插槽
示例:
在Panel组件中有以下模板:
默认标题
默认内容
复制代码
在父组件中使用Panel子组件时,可以这样使用:
新的标题
新的内容
复制代码
11、Mixin 混合
混合可以将组件之间的可复用部分抽离,从而在组件中使用混合时,可以将混合的数据,事件以及方法注入到组件之中。混合分为两种:
默认式混合
兼容式混合
默认混合
对于组件data数据,components组件,events事件以及其它自定义方法采用默认式混合,即如果组件未声明该数据,组件,事件,自定义方法等,那么将混合对象中的选项将注入组件之中。如果组件已声明的选项将不受影响。
// mixins/test.js
import wepy from 'wepy';
export default class TestMixin extends wepy.mixin {
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.page {
data = {
foo: 'foo defined by index'
};
mixins = [TestMixin ];
onShow() {
console.log(this.foo); // foo defined by index 默认的
console.log(this.bar); // bar defined by testMix 使用mixin中的
}
}
复制代码
兼容式混合
对于组件methods响应事件,以及小程序页面事件将采用兼容式混合,即先响应组件本身响应事件,然后再响应混合对象中响应事件。注意,这里事件的执行顺序跟Vue中相反,Vue中是先执行mixin中的函数, 再执行组件本身的函数。
// mixins/test.js
import wepy from 'wepy';
export default class TestMixin extends wepy.mixin {
methods = {
tap () {
console.log('mixin tap');
}
};
onShow() {
console.log('mixin onshow');
}
}
// pages/index.wpy
import wepy from 'wepy';
import TestMixin from './mixins/test';
export default class Index extends wepy.page {
mixins = [TestMixin];
methods = {
tap () {
console.log('index tap');
}
};
onShow() {
console.log('index onshow');
}
}
// index onshow
// mixin onshow
// ----- when tap
// index tap
// mixin tap
复制代码
12、数据绑定
原生小程序的数据绑定方式
this.setData({title: 'this is title'});
复制代码
因为小程序架构本身原因,页面渲染层和JS逻辑层分开的,setData操作实际就是JS逻辑层与页面渲染层之间的通信,那么如果在同一次运行周期内多次执行setData操作时,那么通信的次数是一次还是多次呢?这个取决于API本身的设计。
WePY数据绑定方式
this.title = 'this is title';
复制代码
需注意的是,在异步函数中更新数据的时候,必须手动调用$apply方法,才会触发脏数据检查流程的运行。如:
setTimeout(() => {
this.title = 'this is title';
this.$apply();
}, 3000);
复制代码
13、优化细节
请求
// 原生代码:
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 支持,参考 WIKI
async function request () {
let d = await wepy.request('xxxxx');
console.log(d);
}
复制代码
优化事件参数传递
// 原生的事件传参方式:
Click me!
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。
Click me!
methods: {
tapName (id, title, other, event) {
console.log(id, title, other)// output: 1, wepy, otherparams
}
}
复制代码
组件代替模板和模块
// 原生代码:
{{text}}
var item = require('item.js')
// WePY
{{text}}
import wepy from 'wepy';
import Item from '../components/item';
export default class Index extends wepy.page {
components = { com: Item }
}
复制代码先到这里了,以后再继续补充~~