网上已经有很多前端开发者分享了小程序的入坑心得, 本篇文章不刻意重复造轮子, 面向有过Vue.js
开发经验正准备接触微信小程序开发的读者.
从框架入手
早些版本的原生小程序基础库存在组件化开发困难
和npm模块无法使用
等问题, 腾讯团队开源了wepy解决了老版原生小程序开发的短板. 后来支付宝,今日头条等app相继推出自己的小程序, 于是mpvue跨小程序端框架出现; 再到后来出现了多端开发框架uni-app, taro等.
使用框架开发小程序的开发者可以按自己的需求和习惯选择框架:
- Vue系开发者, 不需要跨端:
wepy
- Vue系需跨端的开发者:
uni-app
- React系需跨端的开发者:
taro
如果你是在纠结选择哪个框架, 笔者推荐uni-app
, 有几点理由:
主要注意的是, 无论使用哪个框架(特别是多端开发框架), 踩坑次数都不会少; 另外小程序基础库也在不断完善, 目前也支持了组件化开发和npm
模块, 喜欢清爽简洁
方式开发的不妨用原生小程序进行开发.
从原生小程序入手
所谓原生小程序开发, 就是不使用上述对小程序基础库和语法二次封装和构建的框架进行小程序开发的方式.
为了写清楚小程序的语法和能力及其生态规范, 说实话官方文档已经做得不错了, 但同时学习成本也不低. 阅读此文档时, 推荐开发者先快速通读
一两遍除服务端相关模块(服务端
,云开发
)外的部分,并阅读以下笔者针对vue开发者给一些"捷径", 快速熟悉小程序开发.然后再到编码时边写变查
, 逐步熟悉.
项目搭建
新建项目
你需要脱离vue-cli
等脚手架工具, 下载微信小程序的官方IDE,然后新建一个小程序项目,其中AppID
是在微信公众平台创建小程序应用时获取,如果没有客店点击使用测试号
,足够开发阶段使用(正式appid才可以正式发布小程序)。
新建成功后就有有了一个可运行的demo
,这里介绍一下最简单的小程序需要哪些目录和文件:
pages
- 存放页面的目录,每个页面自成一个文件夹,需包含同名的js
,wxss
,json
,wxml
文件, 下一小节详细介绍
utils
- 非必需目录,存放工具方法,公共方法等
app.js
- 入口文件,类似vue.js应用中实例化Vue对象的js(一般命名为app.js
或者main.js
)
app.wxss
- 公共样式表(wxss你可以看做css语法子集),这里所写样式都是全局的,你无需在任何文件引入
project.config.json
- IDE的配置目录,无需手动配置
sitemap.json
- 微信搜索索引配置,配置你的某些页面是否可以在当前小程序内被搜索,有兴趣了解的同学可参考文档
当然,实际项目中目录会稍微复杂,以笔者某个小程序项目为例:
有些大家已经熟悉的或者已介绍文件不介绍:
components
- 存放小程序自定义组件目录
resource
- 存放图片、字体等静态文件的目录,习惯上还常命名static
config.js
- 常量配置文件,当然你可以放在其他你喜欢存放的目录
miniprogram_npm
- npm依赖被微信开发者工具二次构建生成的目录,不需手动创建,后面会有npm依赖安装相关的介绍
UI框架
有了基本目录结构,你可能需要一个UI框架来处理反馈(modal、toast、动画)等交互,而不是重新埋头写,这里笔者推荐几个小程序UI框架,具体的安装(推荐npm安装)移步各自文档:
-
vant-weapp
vant
的小程序版本,强烈推荐 -
iview-weapp
iview
的小程序版本,已经较久不更新 - weui 官方出品的微信风格UI,但组件也相对少
构建
编辑器
在发布和调试时,你必须使用微信开发者
工具进行。但在编码时,你仍然可以使用自己喜好的编辑器,并安装小程序的相关辅助插件
来协助开发小程序, 微信会实时监听
项目文件的修改并编译到模拟器。
在小程序使用npm依赖
npm依赖
的安装和引入都和node环境下无差别,但你需在安装好依赖或者有依赖更新
后在微信开发者工具
进行npm构建
:
此时你会发现多了一个
miniprogram_npm
目录,此时我们就可以直接像node环境一样直接引入npm依赖.
当然,一些像
操作DOM
等小程序不支持的对象
的依赖是无法运行的,需注意区分
ES6
你可以使用ES6,考虑到兼容性时,无需借助babel
来转换为ES5,在右上角的详情中勾选:
环境变量
在node环境,可以在打包时注入一个全局变量用来区分环境,以便处理在生产环境、开发环境下的不同逻辑:
#生产环境变量设置,如:
set NODE_ENV=production
#开发环境变量设置,如:
set NODE_ENV=development
但是小程序里,官方一直没有做这方面的支持!所以你需在开发或者发布的时候手动
改一下用来充当环境区分的变量:
//示例,config.js
const ENV = 'product'; //环境变量,手动修改
module.exports = {
ENV,
DOMAIN: ENV === 'product'? 'a.com': 'dev.a.com',
TOKEN_KEY: ENV === 'product'? 'token' : 'token_dev',
//more...
}
编写页面
页面的构造,页面的构造需使用Page方法。详细的属性和生命周期你可以在文档中查看。
为了页面生效,你还必须在根目录下的
app.json
中的pages
字段配上所有页面的路径, 并且pages
数组第一个为小程序进入的首个页面:{ "pages": [ "pages/index/index", //进入小程序时加载的第一个页面 "pages/logs/logs" ], //more... }
新建页面
页面组成文件
假设新建的页面叫page1
,则会生成四个文件:
page1.js
- Page方法的调用,处理页面逻辑
page1.json
- 当前页面的配置文件,比如设置页面背景色、导航栏标题等
page1.wxml
- 页面视图,类似html语法,但你使用的标签需是小程序自有组件或者自定义组件名(下小节介绍);另外,view
标签的地位相当于html中的div
;并且,指令和事件绑定的语法和vue不同(如:v-if
对应wx:if
),具体介绍查看这里,模板
那一块看的不太明白可以先放下,因为你很可能可以使用组件的方式替代它。
page1.wxss
- 你几乎可以把它当成一个css文件来看,只是不需要手动引入就会在当前页面生效。具体与css的差异可查看这里
注意
: 小程序直接修改Page里的data并不会更新视图,你需通过Page.prototype.setData(文档搜索Page.prototype.setData
查看)方法来设置value才可更新视图
组件
在较新的小程序版本已经支持自定义组件,但开发体验笔者认为不如vue的单文件组件。这里做一些简单的对比介绍。
组件化
与页面的构成类似,组件的四个文件作用同页面的一样, 不同的主要有两点:
- 在
js
文件里, 定义组件使用Component构造器. - 在
json
文件的component
字段设为true
,如:
{
"component": true
}
组件的调用
在页面
或者其他组件
目录下的json
文件, 将所有引用的组件的标签名(key,自己取名)和文件路径(value)传入usingComponents
{
"usingComponents": {
"component-tag-name1": "/components/component-tag-name1/component-tag-name1",
"component-tag-name1": "/components/component-tag-name1/component-tag-name1"
//more component...
}
//more config...
}
并在wxml
文件写入组件标签及其传入的参数,事件等:
<component-tag-name1></component-tag-name1>
<component-tag-name2 option="{{yourOptionData}}" bindSomeEvent="eventCallback" />
下面介绍几个传入Component
构造器的几个重要属性.
properties
相当于Vue
中的props
属性, 约定父组件或者页面传入的参数格式:
Component({
properties: {
data1: {
type: Number, //参数的类型
value: 0 //默认值
},
data2: {
type: Number,
optionalTypes: [String, Object], // 可以指定多个属性的类型, 如果type也定义了的话或被计入optionalTypes, 这里的参数可以是 Number 、 String 、 Boolean 三种类型中的一种
observer: function(newVal, oldVal) { //参数值变化时的回调函数, 与vue中`watch`的作用类似
}
}
}
})
behaviors
behavior
相当于vue
中的mixins
, 首先你得先构造一个Behavior:
module.exports = Behavior({
behaviors: [], //behavior里可以引用其他behavior
properties: { //要共享的参数
myBehaviorProperty: {
type: String
}
},
data: { //要共享的data
myBehaviorData: {}
},
attached: function(){}, //要共享的生命周期处理逻辑
methods: { //要共享的方法
myBehaviorMethod: function(){}
}
})
然后在组件中引入:
// my-component.js
var myBehavior = require('my-behavior')
Component({
behaviors: [myBehavior],
properties: {
myProperty: {
type: String
}
},
//more...
})
observers
作用与vue
中的watch
属性类似, 可以监听一个或多个数据变化并执行回调, 但语法上不同,详情请移步文档,这里不再赘述
生命周期
在小程序里, 组件的生命周期与页面的生命周期并不相同(这是因为,在vue
里,无论页面和组件都是vue
实例, 都可以看成组件
, 而在小程序是不一样的实例), 并且App注册小程序时也有自己的生命周期.
小程序组件的生命周期与Vue中的生命周期时间节点基本一致, 只是命名上的差异;并且生命周期方法可以作为和data
同级的属性, 也可以放在lifetimes
属性里,但后者的优先级更高:
Component({
lifetimes: {
attached: function() {
// 在组件实例进入页面节点树时执行
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
},
// 以下是旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容
attached: function() {
// 在组件实例进入页面节点树时执行
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
// ...
})
另外组件还有一组生命周期可使用, 即组件所在页面的生命周期(但并不是页面所有生命周期方法, 也没这个必要), 只有show
,hide
,resize
; 所在页面生命周期回调写在pageLifetimes
属性:
Component({
pageLifetimes: {
show: function() {
// 页面被展示
},
hide: function() {
// 页面被隐藏
},
resize: function(size) {
// 页面尺寸变化
}
}
})
组件通信和事件
与vue的通信方式一样,事件是组件和页面
或者组件和组件
主要和提倡的通信方式, 并且在小程序里, 也倡导单向数据流
规范.
在页面或者组件的wxml
绑定事件:
<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
在组件里触发事件:
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,会作为参数传递到接收事件的方法里
var myEventOption = {} // 触发事件的选项, 主要控制事件是否可冒泡或者可有捕获截断,详情可参考文档
this.triggerEvent('myevent', myEventDetail, myEventOption) //触发事件`myevent`
}
}
})
路由
组件介绍完毕, 小程序的路由机制不依赖其他插件, 由小程序自己控制. 并且开放出来的能力只限于作页面跳转
并可携带参数, 像路由变化回调
等能力并不开放, 跳转携带参数时可在目标页面的onLoad
生命周期方法的参数中接收:
//more...
//假设当前页面路由为'/pages/demo/demo?a=1&b=2'
onLoad: function (options) { //options用于接收上个页面传递过来的参数
console.log(options.a, options.b); // =>1, 2
})
//more...
路由跳转的几个方法你可以直接看文档,这里不详细介绍,只需注意理解的是页面栈
的概念(类似于浏览器环境中的history
对象) ,和跳转到tabbar
页面需用wx.switchTab
方法.
全局变量/全局方法
在vue
的开发过程中, 定义一个全局方法/属性时, 我们通常做法是挂到Vue
对象的原型上:
Vue.prototype.data1 = 'xxx';
Vue.prototype.method1 = ()=>{
};
//在Vue实例里可以方便的访问到:
//more...
mounted(){
console.log(this.data1);
this.method1()
}
//more...
当然,一些经常变动和访问的状态我们会交给Vuex
处理.
小程序里,全局变量/方法可以在App
方法中定义:
App({
onLaunch (options) {
// Do something initial when launch.
},
globalData: { //全局变量放在globalData
data1: '1',
data2: '2'
},
//全局方法直接定义在根级别
method1(){
}
})
调用:
const app = getApp();
Page({
data: {
},
onLoad() {
app.method1();
console.log(app.data1, app.data2); //=>1, 2
}
//more...
})
另外, 你还可以将它们直接挂到global
对象中, 但是一般不推荐
这么做.
global.data1 = '';
调试
微信开发者工具是用开源的chrome内核二次开发, 在微信开发者工具
中调试小程序, 跟在chrome
调试web几乎一样; 可惜的是你并不能下载一些比如'vue.js Devtools'这样的扩展来协助调试, 所以很多时候你只能通过断点
和打印log
来调试. 虽然工具提供了app级别的组件结构和数据视图, 但在笔者看来作用甚微(你可以体验一下):
测试
与vue
的Vue Test Utils类似, 小程序提供miniprogram-simulate用于调试组件, 配合jest
之类的测试框架可以实现组件的测试.
更多细节开发者可以慢慢摸索, 感谢阅读!