小程序wepy.js框架总结
wepy.js借鉴了Vue的语法风格和功能特性,对官方提供的框架进行了封装,更贴近于MVVM架构模式,让开发者更加容易上手,增加开发效率。(脏数据处理--是否有标识、是否有响应)
前端开发的对组件化开发应该都都很熟悉,我们就讲精华的部分:
首先我们需要输入下面的命令:
npm install wepy-cli -g // 全局安装或更新WePY命令行工具 wepy new myproject // 在开发目录中生成Demo开发项目 cd myproject // 切换至项目目录 npm install // 安装依赖 wepy build --watch // 开启实时编译
完成上面的命令后,你会看到类似下面的目录结构(部分文件夹是本人自己的项目业务需要,学习不需要理会):
目录讲解:
--dis
文件夹是微信开发者工具指定的目录(该目录由WePY的build指令自动编译生成,请不要直接修改该目录下的文件)
--src
代码编写的目录(该目录为使用WePY后的开发目录)
--components
wepy组件目录(组件不属于完整页面,仅供完整页面或其他组件引用)
--pages
wepy页面目录(属于完整页面)
--app.wpy
小程序逻辑、公共配置、公共样式
--package.json
项目的package配置
wepy官方文档有五个重要的提示,我们要跟着规则做,不要给自己挖坑。
-
使用
微信开发者工具
-->添加项目
,项目目录
请选择dist
目录。 -
微信开发者工具
-->项目
-->关闭ES6转ES5
。 重要:漏掉此项会运行报错。 -
微信开发者工具
-->项目
-->关闭上传代码时样式自动补全
。 重要:某些情况下漏掉此项也会运行报错。 -
微信开发者工具
-->项目
-->关闭代码压缩上传
。 重要:开启后,会导致真机computed, props.sync 等等属性失效。(注:压缩功能可使用WePY提供的build指令代替,详见后文相关介绍以及Demo项目根目录中的wepy.config.js
和package.json
文件。) -
本地项目根目录运行
wepy build --watch
,开启实时编译。(注:如果同时在微信开发者工具
-->设置
-->编辑器
中勾选了文件保存时自动编译小程序
,将可以实时预览,非常方便。)
用于开发的编辑器,个人喜欢vs code。开发者可以根据自己熟悉的编辑器(微信开发者工具
仅用于实时预览和调试)。
- VS Code
1. 在 Code 里先安装 Vue 的语法高亮插件 Vetur
。
2. 打开任意 .wpy
文件。
3. 点击右下角的选择语言模式,默认为纯文本
。
4. 在弹出的窗口中选择 .wpy 的配置文件关联...
。
5. 在选择要与 .wpy 关联的语言模式
中选择 Vue
。
wepy官方提供了一些代码规范的规则,这里我列出重要的几条:
1.变量与方法尽量使用驼峰式命名,并且注意避免使用$
开头。
2.事件绑定语法使用优化语法代替。
* 原 bindtap="click"
替换为 @tap="click"
,原catchtap="click"
替换为@tap.stop="click"
。
* 原 capture-bind:tap="click"
替换为 @tap.capture="click"
,原capture-catch:tap="click"
替换为@tap.capture.stop="click"
。
3.事件传参使用优化后语法代替。 原bindtap="click" data-index={{index}}
替换为@tap="click({{index}})"
。
4.自定义组件命名应避开微信原生组件名称以及功能标签<repeat>
。 不可以使用input、button、view、repeat
等微信小程序原生组件名称命名自定义组件;另外也不要使用WePY框架定义的辅助标签repeat
命名。
基于WePY的代码:
export default class Catalog extends wepy.page //通过继承自wepy.page的类创建页面逻辑
methods //事件处理函数(集中保存在methods对象中)
components //支持组件化开发如下图
注意:WePY中,在父组件template
模板部分插入驼峰式命名的子组件标签时,不能将驼峰式命名转换成短横杆式命名(比如将childCom
转换成child-com
),这与Vue中的习惯是不一致。
wepy基于原生进行优化:
(使用原生同时最多只能发起5个请求,使用wepy后没有了这个限制)
import wepy from 'wepy'; async onLoad() { await wepy.login(); this.userInfo = await wepy.getUserInfo(); }
一个.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
属性且有效时,会忽略内联代码。
代码如下
<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 、scss 、stylus |
template | wxml |
wxml 、xml 、pug(原jade) |
script | babel |
babel 、TypeScript |
下面具体对脚本进行讲解,
小程序的入口是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>
页面文件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
<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
配置以及页面特有的一些生命周期函数之外,其属性与页面属性大致相同。
通过上面的总结,我们可以知道,小程序被分成三个实例:小程序实例App
、页面实例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小程序实例中主要包含小程序生命周期函数、config配置对象、globalData全局数据对象,以及其他自定义方法与属性。
在Page页面实例中,可以通过this.$parent
来访问App实例。
Page页面实际上继承自Component组件,即Page也是组件。除扩展了页面所特有的config
配置以及特有的页面生命周期函数之外,其它属性和方法与Component一致,因此这里以Page页面为例进行介绍。
注意,对于WePY中的methods属性,因为与Vue中的使用习惯不一致,非常容易造成误解,这里需要特别强调一下:WePY中的methods属性只能声明页面wxml标签的bind、catch事件,不能声明自定义方法,这与Vue中的用法是不一致的。示例如下:
import wepy from 'wepy'; export default class MyComponent extends wepy.component { methods = { bindtap () { let rst = this.commonFunc(); // doSomething }, bindinput () { let rst = this.commonFunc(); // doSomething }, } //正确:普通自定义方法在methods对象外声明,与methods平级 customFunction () { return 'sth.'; } }
需要特别注意的一点:当需要循环渲染WePY组件时(类似于通过wx:for
循环渲染原生的wxml标签),必须使用WePY定义的辅助标签<repeat>
,代码如下:
<template> <!-- 注意,使用for属性,而不是使用wx:for属性 --> <repeat for="{{list}}" key="index" index="index" item="item"> <!-- 插入<script>脚本部分所声明的child组件,同时传入item --> <child :item="item"></child> </repeat> </template>
computed计算属性、watcher监听器跟VUE很相似。
// computed data = { a: 1 } // 计算属性aPlus,在脚本中可通过this.aPlus来引用,在模板中可通过{{ aPlus }}来插值 computed = { aPlus () { return this.a + 1 } } // wathcer 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中属于父子组件之间传值的一种机制,包括静态传值与动态传值。
在props对象中声明需要传递的值,静态传值与动态传值的声明略有不同,具体可参看下面的示例代码。
静态传值
静态传值为父组件向子组件传递常量数据,因此只能传递String字符串类型。
在父组件template
模板部分的组件标签中,使用子组件props对象中所声明的属性名作为其属性名来接收父组件传递的值。
<child title="mytitle"></child> // child.wpy props = { title: String }; onLoad () { console.log(this.title); // mytitle }
动态传值
动态传值是指父组件向子组件传递动态数据内容,父子组件数据完全独立互不干扰。但可以通过使用.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: '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属性值,当在父组件中改变时,会同时改变子组件对应的值。 }
组件之间的通信:
wepy.component
基类提供$broadcast
、$emit
、$invoke
三个方法用于组件之间的通信和交互,如:
this.$emit('some-event', 1, 2, 3, 4);
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
与$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>
当然,所有人写的总结都是基于自己的理解的,因此,个人觉得无论学什么技术,当自己有一定能力可以阅读官方文档后,最好自己把官方的文档都看一遍,只有这样自己的该门技术才有深入的理解。
官方文档地址:https://tencent.github.io/wepy/document.html#/?id=api
如果你阅读到这里了,说明你是个有追求的人,本人额外附送两个链接
https://github.com/hjkcai/wepy-plugin-axios
https://github.com/youzan/zanui-weapp