网上已经有很多前端开发者分享了小程序的入坑心得, 本篇文章不刻意重复造轮子, 面向有过 vue.js 开发经验正准备接触微信小程序开发的读者.
从框架入手
早些版本的原生小程序基础库存在组件化开发困难和 NPM 模块无法使用等问题, 腾讯团队开源了 wepy 解决了老版原生小程序开发的短板. 后来支付宝, 今日头条等 App 相继推出自己的小程序, 于是 mpvue 跨小程序端框架出现; 再到后来出现了多端开发框架 uni-App, taro 等.
使用框架开发小程序的开发者可以按自己的需求和习惯选择框架:
Vue 系开发者, 不需要跨端: wepy
Vue 系需跨端的开发者: uni-App
React 系需跨端的开发者: taro
如果你是在纠结选择哪个框架, 笔者推荐 uni-App, 有几点理由:
比 wepy 更贴近 Vue 开发风格
跨端相对靠谱, 这里有一篇同类框架评测. (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 - 微信搜索索引配置, 配置你的某些页面是否可以在当前小程序内被搜索, 有兴趣了解的同学可参考文档
Hello world!
当然, 实际项目中目录会稍微复杂, 以笔者某个小程序项目为例:
稍微复杂的项目
有些大家已经熟悉的或者已介绍文件不介绍:
components - 存放小程序自定义组件目录
resource - 存放图片, 字体等静态文件的目录, 习惯上还常命名 static
config.JS - 常量配置文件, 当然你可以放在其他你喜欢存放的目录
miniprogram_npm - NPM 依赖被微信开发者工具二次构建生成的目录, 不需手动创建, 后面会有 NPM 依赖安装相关的介绍
UI 框架
有了基本目录结构, 你可能需要一个 UI 框架来处理反馈 (modal,toast, 动画) 等交互, 而不是重新埋头写, 这里笔者推荐几个小程序 UI 框架, 具体的安装 (推荐 NPM 安装) 移步各自文档:
vant-weapp vant 的小程序版本, 强烈推荐
iview-weapp iview 的小程序版本, 已经较久不更新
weui 官方出品的微信风格 UI, 但组件也相对少
构建
编辑器
在发布和调试时, 你必须使用微信开发者工具进行. 但在编码时, 你仍然可以使用自己喜好的编辑器, 并安装小程序的相关辅助插件来协助开发小程序, 微信会实时监听项目文件的修改并编译到模拟器.
vscode 示例
在小程序使用 NPM 依赖
NPM 依赖的安装和引入都和 node 环境下无差别, 但你需在安装好依赖或者有依赖更新后在微信开发者工具进行 NPM 构建:
在右上角的详情中勾选
构建 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 方法. 详细的属性和生命周期你可以在文档中查看.
新建页面
在 pages 目录右键 - 新建一个页目录
在页面目录右键 - 新建页面文件
页面组成文件
假设新建的页面叫 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 级别的组件结构和数据视图, 但在笔者看来作用甚微(你可以体验一下):
查看页面的 data 和页面构成
测试
与 vue 的 Vue Test Utils 类似, 小程序提供 miniprogram-simulate 用于调试组件, 配合 jest 之类的测试框架可以实现组件的测试.
来源: http://www.jianshu.com/p/681eb7dbcbfb