title: 小程序教程之wepy
参考文档
VSCode自动补齐Vue语法
打卡VScode、首选项 、设置,或者直接Comand+,快捷键
补全的语法为类Vue语法
用户设置一栏中改成:
// { // "window.zoomLevel": 0, // "editor.fontSize": 16, // "files.associations": { // "*.wpy": "vue", // "*.wxss": "css", // "*.wxml": "html" // } // } // Place your settings in this file to overwrite the default settings { // 配置 glob 模式以排除文件和文件夹。 "files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, // "**/node_modules": true, "**/bower_components": true // "**/dist": true }, // 配置语言的文件关联(如: "*.extension": "html")。这些关联的优先级高于已安装语言的默认关联。 "files.associations": { "*.vue": "vue", "*.wpy": "vue", "*.wxss": "postcss", "*.tpl": "vue", "*.md@xxx": "markdown", "*.wepy": "vue" }, // 一个制表符等于的空格数。该设置在 `editor.detectIndentation` 启用时根据文件内容进行重写。 "editor.tabSize": 2, // 当打开文件时,将基于文件内容检测 "editor.tabSize" 和 "editor.insertSpaces"。 "editor.detectIndentation": false, "window.zoomLevel": 1, "editor.wordWrap": "on", "workbench.iconTheme": "vs-seti", "editor.wordSeparators": "`~!@#$%^&*()=+[{]}\\|;:'\",.<>/?。", "editor.minimap.enabled": true, // 控制键入时是否应自动显示建议 "editor.quickSuggestions": { "other": true, "comments": true, "strings": true }, // 配置 glob 模式以在搜索中排除文件和文件夹。从 files.exclude 设置中继承所有 glob 模式。 "search.exclude": { "**/node_modules": true, "**/bower_components": true, "**/dist": true }, // 控制编辑器是否应呈现缩进参考线 "editor.renderIndentGuides": true, "emmet.syntaxProfiles": { "vue-html": "html", "vue": "html" }, // 配置内置 HTML 语言支持是否建议 Angular V1 标记和属性。 "html.suggest.angular1": false, // 自动更新扩展 "extensions.autoUpdate": true, // 对属性进行换行。 "html.format.wrapAttributes": "auto", // Link file types to the beautifier type "beautify.language": { "js": { "type": [ "javascript", "json" ], "filename": [ ".jshintrc", ".jsbeautify", ".eslintrc.js" ] }, "css": [ "css", "scss" ], "html": [ "htm", "html" ] }, "git.enabled": false, "editor.renderControlCharacters": true, "typescript.check.npmIsInstalled": false, "extensions.ignoreRecommendations": false, // 覆盖当前所选颜色主题的颜色。 此为实验性设置,因为下一版本中将更改颜色名称。 // "workbench.experimental.colorCustomizations": { // "statusBarBackground": "#666666", // "panelBackground": "#555555", // "sideBarBackground": "#444444" // } // When enabled, emmet abbreviations are expanded when pressing TAB. "emmet.triggerExpansionOnTab": true, "workbench.panel.location": "bottom", "editor.fontSize": 16 }
over
快速入门
初始化项目
1.全局安装或者更新wepy命令行工具 $ npm install wepy-cli -g 2.在开发目录中生成Demo开发项目 wepy new myproject # 1.7.0之后的版本使用 wepy init standard myproject 初始化项目,使用 wepy list 查看项目模板 3.编译 $ cd project $ npm install $ wepy build --watch 或者 $ npm run dev
项目目录
├── 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配置
重要提醒
1. 微信开发者工具添加dist目录 2. 微信开发者工具关闭ES6转ES5 3. 运行 wepy build --watch 实时编译 。。。
代码高亮和代码规范
over
主要功能特性
开发模式转换
原生代码 //index.js //获取应用实例 var app = getApp() //通过Page构造函数创建页面逻辑 Page({ //可用于页面模板绑定的数据 data: { motto: 'Hello World', userInfo: {} }, //事件处理函数 bindViewTap: function() { console.log('button clicked') }, //页面的生命周期函数 onLoad: function () { console.log('onLoad') }
基于wepy的代码
//index.wpy中的<script>部分 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'); }; }
支持组件式开发
// 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'; //引入List、Panel和Counter组件 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>
支持加载外部npm包
单文件模式,目录结构更加清晰,开发方便
WePY中则使用了单文件模式,将原生小程序app实例的3个文件统一为
app.wpy
,page页面的4个文件统一为page.wpy
默认babel编译,支持ES7新特性
针对原生API进行优化,如wx.request的并发问题等。
over
进阶介绍
wepy.config.js配置文件
执行
wepy new demo
后,会生成类似下面的配置文件let prod = process.env.NODE_ENV === 'production'; module.exports = { 'output': 'dist', 'source': 'src', 'wpyExt': '.wpy', 'compilers': { less: { 'compress': true }, /*sass: { 'outputStyle': 'compressed' },*/ babel: { 'presets': [ 'es2015', 'stage-1' ], 'plugins': [ 'transform-export-extensions', 'syntax-export-extensions', 'transform-runtime' ] } }, 'plugins': { } }; if (prod) { // 压缩sass module.exports.compilers['sass'] = {'outputStyle': 'compressed'}; // 压缩less module.exports.compilers['less'] = {'compress': true}; // 压缩js module.exports.plugins = { 'uglifyjs': { filter: /\.js$/, config: { } }, 'imagemin': { filter: /\.(jpg|png|jpeg)$/, config: { 'jpg': { quality: 80 }, 'png': { quality: 80 } } } }; } wpyExt: 默认值为.wpy compilers: sass\less\stylus\babel\typscript plugins
.wepy文件说明
.wepy 文件编译过程:
.wpy文件分为三个部分
- 脚本部分,即
<script></script>
标签中的内容,又可分为两个部分:逻辑部分,除了config对象之外的部分,对应于原生的
.js
文件;配置部分,即config对象,对应于原生的
.json
文件。
- 结构部分,即
<template></template>
模板部分,对应于原生的.wxml
文件。
样式部分,即
<style></style>
样式部分,对应于原生的.wxss
文件。
.wpy
文件中的script
、template
、style
这三个标签都支持lang
和src
属性,lang
决定了其代码编译过程,src
决定是否外联代码,存在src
属性且有效时,会忽略内联代码。各标签对应的
lang
值如下表所示:
标签 lang默认值 lang支持值 style css
css
、less
、scss
、stylus
template wxml
wxml
、xml
、pug(原jade)
script babel
babel
、TypeScript
over
over
脚本部分介绍
小程序入口app.wpy
<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> <style lang="less"> /** less **/ </style> 入口文件app.wpy中所声明的小程序实例继承自wepy.app类,包含一个config属性和其它全局属性、方法、事件。其中config属性对应原生的app.json文件,build编译时会根据config属性自动生成app.json文件,如果需要修改config中的内容,请使用微信提供的相关API。
页面page.wpy
<script> 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 } </script> <template lang="wxml"> <view> </view> <counter1></counter1> </template> <style lang="less"> /** less **/ </style>
属性 说明 config 页面配置对象,对应于原生的 page.json
文件,类似于app.wpy
中的configcomponents 页面组件列表对象,声明页面所引入的组件列表 data 页面渲染数据对象,存放可用于页面模板绑定的渲染数据 methods wxml事件处理函数对象,存放响应wxml中所捕获到的事件的函数,如 bindtap
、bindchange
events WePY组件事件处理函数对象,存放响应组件之间通过 $broadcast
、$emit
、$invoke
所传递的事件的函数其它 小程序页面生命周期函数,如 onLoad
、onReady
等,以及其它自定义的方法与属性组件com.wpy
<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> <style lang="less"> /** less **/ </style> 组件文件com.wpy中所声明的组件实例继承自wepy.component类,除了不需要config配置以及页面特有的一些生命周期函数之外,其属性与页面属性大致相同。
over
实例
实例
wepy中,小程序被分为三个实例:
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小程序实例
在Page页面实例中,可以通过
this.$parent
来访问App实例。Page页面实例和Component组件实例
Page实际上继承自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 = {}; // 声明组件之间的事件处理函数 } WePY中的methods属性只能声明页面wxml标签的bind、catch事件,不能声明自定义方法,这与Vue中的用法是不一致的
组件
WePY中实现了小程序的组件化开发,组件的所有业务与功能在组件本身实现,组件与组件之间彼此隔离
普通组件引用
当页面需要引入子组件时,
必须 在
.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>
组件的循环渲染
当需要循环渲染WePY组件时(类似于通过
wx:for
循环渲染原生的wxml标签),必须使用WePY定义的辅助标签<repeat>
// 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>
computed计算属性
computed计算属性,是一个有返回值的函数,可直接被当做绑定数据来使用。需要注意的是,只要是组件中有任何数据发生了改变,那么所有计算属性就都会被重新计算
data = { a: 1 } // 计算属性aPlus,在脚本中可通过this.aPlus来引用,在模板中可通过{{ aPlus }}来插值 computed = { aPlus () { return this.a + 1 } }
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) }
props传值
传值包括三种:父组件向子组件传值,子组件向父组件传值,双向传值
props传值在WePY中属于父子组件之间传值的一种机制,包括静态传值与动态传值。
<child title="mytitle"></child> // child.wpy props = { title: String }; onLoad () { console.log(this.title); // mytitle }
动态传值,双向绑定
动态传值是指父组件向子组件传递动态数据内容,父子组件数据完全独立互不干扰。但可以通过使用
.sync
修饰符来达到父组件数据绑定至子组件的效果,也可以通过设置子组件props的twoWay: true
来达到子组件数据绑定至父组件的效果。那如果既使用.sync
修饰符,同时子组件props
中添加的twoWay: true
时,就可以实现数据的双向绑定了。// 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: 'nothing', twoWay: true }
};
onLoad () {
console.log(this.title); // p-title
console.log(this.syncTitle); // p-title
console.log(this.twoWayTitle); // p-titlethis.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属性值,当在父组件中改变时,会同时改变子组件对应的值。
}
7. 组件通信与交互 `wepy.component`基类提供`$broadcast`、`$emit`、`$invoke`三个方法用于组件之间的通信和交互,如: ```js 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
广播
$emit
出发
$invoke
直接调用
组件自定义事件处理函数
可以通过使用
.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>
slot组件内容分发插槽
相当于占位符
在Panel组件中有以下模板 <view class="panel"> <slot name="title">默认标题</slot> <slot name="content">默认内容</slot> </view> 在父组件中使用Pannel子组件时,可以这样使用: <panel> <view slot="title">新的标题</view> <view slot="content"> <text>新的内容</text> </view> </panel>
over
第三方组件
WePY允许使用基于WePY开发的第三方组件,开发第三方组件规范请参考wepy-com-toast。
Mixin混合
混合可以将组之间的可复用部分抽离,从而在组件中使用混合时,可以将混合的数据,事件以及方法注入到组件之中。混合分分为两种:
- 默认式混合
- 兼容式混合
- 默认式混合
- 兼容式混合
WXS
interceptor拦截器
over
数据绑定
原生小程序数据绑定方式
this.setData({title: 'this is title'});
wepy数据绑定方式
WePY使用脏数据检查对setData进行封装,在函数运行周期结束时执行脏数据检查,一来可以不用关心页面多次setData是否会有性能上的问题,二来可以更加简洁去修改数据实现绑定,不用重复去写setData方法
this.title = 'this is title';
需注意的是,在异步函数中更新数据的时,必须手动调用
$apply
方法,才会触发脏数据检查流程的运行setTimeout(() => { this.title = 'this is title'; this.$apply(); }, 3000);
wepy脏数据检查流程
over
其他优化细节
- wx.request 接受参数修改
- 优化事件参数传递
- 改变数据绑定方式
- 组件地带模板和模块
实战开发
文件结构
src/components/config 文件夹结构
api.js 存放借口给 config.js存放全局、服务器地址等常量 utils.js 存放 toast、网络请求等方法
src/pages/diary文件夹结构
暂无
微信开发者工具自定义编译
0.查看Network列表(在Console旁边),该列表为当前页面所有接口,查看info发现,Response返回值 1.打开微信开发者工具 2.在上方编译处选择 添加编译模式 3.自定义编译条件 模式名称:便于记录 日记详情页 启动页面:打开相应页面,会自动生成 pages/diary/DiaryInfo 启动参数 catDiaryId=1513 进入场景
页面导航
over
开发流程
- 熟悉目录结构
- 熟悉服务端接口流程
- 先开发网络请求
- 构造数据
- 拆解sketch原型,开始template页面开发
布局
参考文献:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
- 盒子模型
- Flex布局
wepy API
wepy.app Class
$wxapp:Object: this.$wxapp等同于getApp()。 $pages:List<Page>: 所有页面列表 $interceptors:List<Object>:所有拦截器列表 use(middleWare:String|Function):使用中间件。
wepy.component Class
属性 $name:String: 组件名称。 $isComponent:Boolean:是否是组件,如果是页面,此值为false。 $wxpage:Object: 小程序原生page。 $parent:Page|App:组件的父组件,如果当前是组件是Page对象,那么$parent的值为App对象。 $root:Page: 组件所在的Page对象,如果当前组件是Page对象,那么$root的值就是自己本身。 $coms:List<Component>:组件的子组件列表。 $mixins:Array[Mixin]:组件所注入的Mixin对象。 data:Object: 组件需要绑定的数据。 methods:List<Function>:组件需要响应的事件列表。 props:List<Props>:组件允许传递的props列表。 events:List<Function>:组件通信时所需要的事件表现。 方法 getCurrentPages():相当于全局方法getCurrentPages()。 $getComponent(com:String):通过组件名称路径查找组件对象。 $invoke(com:String|Component):调用另一组件的方法。 // coma.js this.$invoke('./ComB', 'func1', 'p1', 'p2'); this.$invoke('./ComB', 'func2', 'p1', 'p2'); // comb.js export class ComB extends wepy.component { methods = { func1 (p1, p2, evt) {} }, func2 (p1, p2) {} } $broadcast(eventName:String, [args]):组件发起一个广播事件。 // page1.js components = { ComA }; this.$broadcast('broadcast-event', 'p1', 'p2'); // coma.js events = { 'broadcast-event': (p1, p2, event) {} } $emit(eventName:String, [args]):组件发起一个冒泡事件。 // coma.js this.$emit('emit-event', 'p1', 'p2'); // page1.js components = { ComA }; events = { 'emit-event': (p1, p2, event) => {} } $apply([func:Function]):组件发起脏检查。 正常流程下,改变数据后,组件会在流程结束时自动触发脏检查。 在异步或者回调流程中改变数据时,需要手动调用$apply方法。 $nextTick([func:Function]):组件数据绑定完成后的回调事件,v1.6.1以上可用。
wepy.page Class
页面类,继承自wepy.component,拥有页面所有的属性与方法。 $preload(key:String|Object, [value:Object]):给页面加载preload数据 $redirect(url:String|Object, [params:Object]):wx.redirectTo的封装方法。 this.$redirect('./page2', {a: 1, b: 2}); this.$redirect({ url: './pages?a=1&b=2' });
wepy.event Class
wepy.mixin Class
Mixin基类,用于复用不同组件中的相同功能。 // mymixn.js export class MyMixin extends wepy.mixin { // my logic here } // mycom.js import MyMixin from './mymixin'; export class MyCom extends wepy.component { mixins = [MyMixin]; }
over