# 基础篇 1:小程序开发基础知识

# 准备工作

小程序开发要先注册小程序账号,有了小程序账号才可以使用开发者工具。小程序是一种特殊的开发形式,里面的 API 和组件都是自己定制的,因此在普通的浏览器中不能预览,要预览功能和页面就需要使用开发者工具。

# 注册小程序账号

先准备一个没有注册过公众号的邮箱,然后访问小程序介绍页面 (opens new window)并点击底部的「前往注册」按钮,再按照提示填写个人信息,最后进入邮箱激活账号即可。

详细流程请参考官方文档 (opens new window)。

# 安装开发者工具

小程序有自己的开发者工具,可以编写代码,实时查看页面和功能效果,还能在开发者工具中进行 debug。小程序开发者工具是使用 NW.js 编写的。

开发者工具下载地址:微信开发者工具 (opens new window)

# 使用开发者工具演示 WeUI

WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含 button、cell、dialog、 progress、 toast、article、actionsheet、icon 等各式组件。

WeUI 有两个版本,一个是普通的 HTML5 版本 (opens new window),另外是小程序版本 (opens new window)。本节我们通过 WeUI 来简单学习开发者工具的使用。

首先下载 WeUI 源码:

# https 方式(推荐)
git clone https://github.com/Tencent/weui-wxss.git
# 或者 ssh 方式
git clone git@github.com:Tencent/weui-wxss.git

打开开发者工具,使用注册时绑定的微信账号扫码登录,这时在进入的界面中选择「小程序项目」,如果是初次使用小程序开发者工具,没有创建过小程序项目,就会进入下面的页面:

在这个页面,先选择我们 clone 下来的 WeUI 项目的 dist 文件夹。对于 AppID 选项,如果已经有了小程序账号,可以在账号后台找到 AppID 并填写上;若还没有注册小程序账号,可以直接在「或使用测试号:」后面单击「小程序」,就会自动填好。然后给项目起个名字,比如「WeUI 演示」,点击确定后就打开了 WeUI 项目。这时候看到的是开发者工具的开发界面,如下图所示,开发界面主要由三部分组成:模拟器、编辑器和调试器。

  • 模拟器:提供小程序的运行环境,模拟小程序在手机上的界面效果
  • 编辑器:简单 IDE 功能,点击左侧树形菜单可以打开多个文件直接编写保存,做到实时预览效果,但是开发者工具的编辑器做得比较简单,而且使用体验并不好,建议选择自己顺手的 IDE 增加 WXML 和 WXSS 的语法高亮插件等来编辑代码
  • 调试器:订制版的 Chrome 开发者工具,提供从页面结构到网络请求等多个面板支持,会用 Chrome DevTools 就很容易上手该工具

Tips: 常用 IDE 推荐

除了三个重要组成部分之外,在开发者工具的顶部还有各种操作按钮。左侧主要是模拟器、编辑器、调试器和小程序云开发控制台的视图开关,可以控制对应视图的开启关闭。

中间部分是跟开发、编译、测试和上线相关的各种按钮,我们在开发和测试小程序中会经常使用,最常用的有:

  • 预览、远程调试:是可以在手机上直接预览效果,开启远程 debug 功能的
  • 清缓存:对于一些授权登录、缓存、数据之类的操作,我们需要清理状态和数据,可以通过这个按钮操作
  • 上传:如果是创建项目的时候填写了 AppID,那么会出现这个按钮,小程序开发完毕后可以通过这个来上传,上传之后可以在小程序后台申请测试版和审核,审核通过后就可以正式上线了

最后介绍的是开发者工具右上角的「详情」。

详情下面主要有三个 Tab:项目设置、域名信息和腾讯云状态。

  • 项目设置:这个是用得比较多的,可以指定上传代码时候的编译情况,比如支持 ES6 语法、支持 css autoprefixer、代码压缩等
  • 域名信息:小程序的 request 等访问的域名采用了白名单形式,在这里可以看到小程序管理后台设置的域名白名单
  • 腾讯云状态:可以看到小程序账号和腾讯云的绑定情况

# 小程序开发语言

小程序的开发语言跟前端开发者比较熟悉的 HTML5 非常相似(甚至相同),小程序的视图层由 WXML 和 WXSS 组成,分别对应 HTML 和 CSS,逻辑层则跟 HTML5 一样,也是 JavaScript 语言实现。

  • WXML:小程序自己发明的 XML 语法描述,用来构造小程序的页面结构
  • WXSS:小程序的页面的样式表语言,描述 WXML 的样式
  • JavaScript:小程序 JS 的执行环境并不是普通的 WebView 浏览器,也不是 Node.js 环境,它执行在微信 App 内上下文, 跟 Node.js 一样,也不能像在浏览器内一样对页面 DOM 进行操作

微信小程序运行在三端:iOS、Android 和用于调试的开发者工具

  • 在 iOS 上,小程序的 JavaScript 代码运行在 JavaScriptCore 中
  • 在 Android 上,小程序的 JavaScript 代码通过 X5 内核来解析
  • 在 开发工具上, 小程序的 JavaScript 代码运行在 NW.js(Chromium 内核) 中

除了普通的 JavaScript,小程序还支持一种类似 JS 的 WXS 语言,WXS 对于小程序开发不是必需的,它的主要目的是为了增强 WXML 的数据处理能力而新引入一种技术实现,其实际解析的语言规范还是 JS,并没有引入新的语法,仅仅对 JS 做了上层的封装和限制,所以学习上基本没什么成本,大致了解下开发文档马上就能上手。本小册实战部分也会涉及简单的 WXS 编写。

对于 WXS 和 JavaScript 的性能比较,官方给出的数据是:由于运行环境的差异,在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍。在 Android 设备上二者运行效率无差异。

# 小程序项目相关知识

# 小程序目录结构

小程序项目由配置文件、页面文件、静态资源和其他相关(比如组件、小程序云函数等)内容组成,一般小程序会由四类文件组成:

  • .json 后缀的 JSON 配置文件
  • .wxml 后缀的 WXML 模板文件
  • .wxss 后缀的 WXSS 样式文件
  • .js 后缀的 JS 脚本逻辑文件

小程序项目的目录结构组成没有严格的要求,按照前端项目的经验,一般会分为:配置、页面、静态资源、基础库、组件等多个目录,例如下面的目录结构:

├── app.js            小程序全局app相关js
├── app.json          小程序配置文件
├── app.wxss          小程序全局app样式
├── cloud-functions   云函数目录
│   ├── decrypt
│   ├── geocoder
│   ├── he-air
│   ├── he-weather
│   ├── jscode2session
│   └── weather
├── components        组件库
│   └── icon
├── images            图片等静态资源
│   └── cloud.jpg
├── pages             页面目录
│   ├── diary
│   └── weather
└── project.config.json  工具项目配置文件

当然根据不同的项目,可能目录结构不同,但是小程序必需的 app.json 和页面组成是必不可少的。另外,在开发复杂的项目时,我们会用到开发框架或者编译工具,这时候目录结构只需要保证编译之后的目录结构符合规范即可。

# 小程序的配置

小程序有三个重要的配置,分别放在三个 JSON 文件内:project.config.json(工具项目配置)、app.json(小程序配置)、page.json(单页面配置)

  • project.config.json:这个是配置项目工具相关的,比如开发者工具的编译设置(是否使用 ES6 语法等)、界面设置,以及云函数相关的 cloudfunctionRoot,详细可以参考项目配置文件 (opens new window)
  • app.json:小程序的全局配置,包括了所有页面路径、界面表现、网络超时时间、底部 tab、插件等,常用的两个配置是 window 和 pages,详细配置参考全局配置 (opens new window)
  • page.json:是相对于 app.json 更细粒度的单页面配置,详细参考页面配置 (opens new window)

# 组件和插件

小程序页面是由各种组件组成的,组件可以类比成原生 HTML5 的标签。

# 组件

小程序内部定义了很多组件,可以对应 HTML5 的标签和基础能力来理解,小程序的组件根据实现不同,可以分为 Web 组件和 Native 组件,Web 组件是由 HTML5 原生 Web 组件封装的组件,比如 view、image 等;Native 组件是为了增强小程序的体验,用客户端技术实现的组件,包括一些交互复杂、原生 Web 组件性能不高的组件,例如 input、map、canvas、video 等。

小程序一共提供 8 大类 30 多个组件:

  • 视图容器:主要是实现页面布局的,对常见的布局形式进行了封装,比如滚动 sroll-view 等
  • 基本内容:类似 HTML5 中内容相关的 p、em 等
  • 表单相关:要比 HTML5 的 form 表单丰富一些
  • 导航:类似 a 标签
  • 媒体:类比 HTML5 中的 video、audio 和 img 等,但是提供更标准的界面和更丰富的 API 支持
  • 画布:Native 实现的 Canvas
  • 地图:结合腾讯地图数据 Native 实现的组件
  • 开放能力:这部分组件偏通用和小程序业务

# 自定义组件

小程序本身支持很多组件,比如地图、按钮等,开发者也可以自己做项目内公共组件,比如我们后面实战部分会介绍做一个 icon 组件,放在 components 目录下面,这样此小程序的任何页面如果要使用这个 icon 公共组件,只需在自己的 page.json 中添加如下字段:

"usingComponents": {
  "icon": "../../components/icon/index"
}

添加完成之后,在页面代码中就可以直接使用 <icon> 的 tag 了。官方文档 (opens new window)有更加详细的介绍。

# 插件

插件是对一组 JS 接口、自定义组件或页面的封装,用于提供给第三方小程序调用。简单来说,插件是组件的升级版本,组件只能在自己项目中使用,插件则更独立,是可以发布到全网,供其他开发者使用的。例如实战中,笔者使用了一款日历插件,则需要在 app.json 中增加 plugins 字段:

"plugins": {
  "calendar": {
    "version": "1.1.3",
    "provider": "wx92c68dae5a8bb046"
  }
}

如果想开发一个插件,则可以参考官方文档 (opens new window)。

# 小程序开发基础

微信小程序是由数据驱动的开发框架,本小节主要介绍小程序开发中的基础概念和知识。

# 数据驱动

微信小程序是数据驱动模型,在 WXML 中可以对页面的数据进行绑定,小程序的 WXML 内使用的是 Mustache 语法,在 {{}} 内可以将变量名包裹起来。 例如:

<view>{{ message }}</view>

Page({
  data: {
    message: 'Hello MINA!'
  }
})

但是小程序不支持复杂的表达式,目前支持简单的三元、加减和逻辑判断,如果要使用形如 NaN 的函数调用语法,需要 WXS 支持:

WXML 内容:

<wxs src="./demo.wxs" module="tools" />
<view>{{ tools.toNumber(num) }}</view>

WXS 内容:

// demo.wxs
function toNumber(n){
  return parseInt(n)
}
module.exports.toNumber = toNumber

小程序内对页面的数据修改只能通过 setData 方法,不能使用直接赋值的方式 this.data.key = value:

Page({
  data: {
    message: 'Hello MINA!'
  },
  onLoad(){
    this.setData({
      message: 'hello world~'
    })
  }
})

记住:修改页面数据,只能使用 this.setData 修改!

# 事件绑定和处理

在小程序内,除了标准 HTML5 中遇见的 touchstart 等事件外,增加了 tap 类的事件,主要包括以下几种:

事件名称

说明

tap

手指触摸后马上离开

longpress

手指触摸后,超过 350ms 再离开,如果指定了事件回调函数并触发了这个事件,tap 事件将不被触发

longtap

手指触摸后,超过 350ms 再离开(推荐使用 longpress 事件代替)

小程序内,事件的绑定是通过在 WXML 标签增加 bind* 属性来实现的,比如新鲜天气的生活指数,笔者是绑定了一个 tap 事件,当用户点击之后,会响应对应页面 JS 的函数:

<view class="life-style">
    <view class="item" wx:for="{{lifeStyle}}" data-name="{{item.name}}" data-detail="{{item.detail}}" bindtap="indexDetail">
      <view class="title">
        <icon type="{{item.icon}}"></icon>
        {{item.name}}
      </view>
      <view class="content">{{item.info}}</view>
    </view>
</view>

上面的代码绑定了 tap 事件,事件处理函数为 indexDetail。

# 事件冒泡

小程序内的事件分为可冒泡和不可冒泡的事件,除了 submit、input 之类的事件,多数是可冒泡的事件,对于事件的绑定,除了 bind* 的方式,还可以通过 catch* 的方式来绑定,两者的区别在于:

  • bind 不会阻止冒泡,变形写法为 bind:*
  • catch 会阻止事件继续冒泡,变形写法为 catch:*

# 事件捕获

小程序内,触摸类事件支持捕获阶段,捕获是先于冒泡的触发,绑定捕获事件,可以使用 capture-bind、capture-catch,后者将中断捕获阶段和取消冒泡阶段,下面是官方的示例:

在下面的代码中,点击 inner view 会先后调用 handleTap2、handleTap4、handleTap3、handleTap1。

<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

如果将上面代码中的第一个 capture-bind 改为 capture-catch,将只触发 handleTap2。

<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

关于事件的详细说明,建议阅读官方的文档 (opens new window),以获取更大的帮助。

# 事件对象

当事件触发时,处理函数会响应,传入 event 对象,通过 event 对象可以获取事件触发时候的一些信息,包括时间戳、detail 等。

因为小程序内的事件绑定都是在 WXML 中实现的,所以传递参数只能通过 WXML 上面的属性值来传递,例如下面的代码中,indexDetail 处理函数需要接收生活指数的名称和详情,来弹出弹层提示,这时候需要在标签上增加 data-xx 这样的属性,data-name 和 data-detail 就是两个属性,通过这两个值,可以在 indexDetail 内 event 对象的 target/currentTarget 的 dataset 获取参数。

<view class="life-style">
    <view class="item" wx:for="{{lifeStyle}}" data-name="{{item.name}}" data-detail="{{item.detail}}" bindtap="indexDetail">
      <view class="title">
        <icon type="{{item.icon}}"></icon>
        {{item.name}}
      </view>
      <view class="content">{{item.info}}</view>
    </view>
</view>

// weather/index.js
// 响应事件的处理函数
indexDetail(e) {
  const {name, detail} = e.currentTarget.dataset
  wx.showModal({
    title: name,
    content: detail,
    showCancel: false
  })
}

按照官方文档,target 和 currentTarget 都有个 dataset,正确获取 dataset 的姿势是使用 currentTarget 的,但是有时候 target 和 currentTarget 的数据又是完全一样的,如果这里使用 target 的话,那么有时候点击会弹出弹窗,有时候不会弹出,这两者究竟是怎样的关系呢?官方的解释有点模棱两可:

  • target:触发事件的源组件
  • currentTarget:事件绑定的当前组件

这里笔者做下详细解释:

  • target:触发事件的源组件,上面的代码中,target 可能是 view.title、view.content、view.item 任意触发事件的组件
  • currentTarget:事件绑定的当前组件,上面的代码中,只能是真正绑定了 bindtap 的 view.item

下面再来看下例子:

<view id="outer" bindtap="handleTap1">
  outer view
  <view id="middle" catchtap="handleTap2">
    middle view
    <view id="inner" bindtap="handleTap3">
      inner view
    </view>
  </view>
</view>

点击 inner view 时,handleTap3 收到的事件对象 target 和 currentTarget 都是 inner,而 handleTap2 收到的事件对象 target 就是 inner,currentTarget 就是 middle。

由此一看,可以简单总结出来:target 是事件触发源头的地方,即事件开始的地方,可以冒泡到父节点触发父节点的绑定事件;而 currentTarget 是开发者自己绑定事件的地方,即实际的绑定事件的节点。所以,如果绑定的事件有子节点,那么 target 不会等于 currentTarget,有可能是冒泡触发的,由此可见,获取 dataset 的时候使用 currentTarget 是靠谱的。

# 小程序的事件驱动和数据绑定模型

由上面数据驱动和事件监听的处理方式可见,小程序是一套数据和事件驱动的模型,即下面的形式:

关于小程序的运行机制,在第 4 节 (opens new window)中会有更加详细的介绍。

# 路由

在小程序内,不能像 HTML5 中 a 标签那样,随便跳转,也不能像 location 对象中对应的属性那样随意跳转,小程序提供了对应 a 标签和 location 对象的方法:navigator 组件 (opens new window)和 wx 中的导航相关函数 (opens new window)。

在小程序中,路由是由路由栈来维护的,小程序的路由栈中最多维护 5 个页面,这样在 5 个页面内,小程序维护其渲染页面,可以实现快速的切换。

小程序中跳转页面可以通过下面两种方式:

  1. 使用 navigator 组件:
<navigator url="跳转页面URL" >跳转到新页面</navigator>

  1. 使用 wx 中的导航相关函数:
<view bindtap="gotoUrl">跳转页面</view>

Page({
  gotoUrl(){
    let url = 'pages/another/url'
    wx.navigateTo({
      url
    })
  }
})

# JavaScript 的限制和增强

微信内的 JavaScript 相对于浏览器中的有限制也有增强,增强的部分是基于小程序 Native 端能力做的增强,比如增强的文件操作类(相册、录音等);除了增强,跟 HTML5 浏览器环境最大的不同是限制部分。

小程序的执行环境是没有浏览器了,所以浏览器环境特有的 window 对象、BOM 和 DOM 等相关 API 都存在缺失(有对应的补充 API),小程序的执行环境是类似于 Node.js 的一种执行环境。因为没有浏览器环境,所以跟浏览器相关的操作如 cookie、Ajax 请求(XMLHttpRequest)、DOM 选择器、DOM 事件、路由 history、缓存、设备信息、位置等都不存在,与之相对应的是小程序的私有 API,比如我们在小程序中不能使用 XMLHttpRequest,但是可以使用功能更加强大的 wx.request 方法。

# 小程序布局相关知识

本小节介绍小程序布局相关的知识。

# rpx

小程序 WXSS 中使用了 rpx 这个长度单位,可以用于表示元素的宽高和边距、字体的大小等。对于习惯使用 px 或者 rem 来做页面的前端来说,这可能让人有点迷糊。rpx 是以小程序容器宽度(等于设备宽度)恒等于 750rpx 来做定义的。对于 iPhone 6 来说,因为 dpr 为 2,所以 iPhone 的宽度为 375px,这样在 iPhone 6 上使用 rpx 的话,换算关系为 2rpx=1px。根据这样的关系类推,得到官方给的表格:

设备 rpx换算 px (屏幕宽度/750) px 换算 rpx (750/屏幕宽度)
iPhone 5 1rpx = 0.42px 1px = 2.34rpx
iPhone 6 1rpx = 0.5px 1px = 2rpx
iPhone 6 Plus 1rpx = 0.552px 1px = 1.81rpx

看起来很麻烦,但是只需要按照官方建议,让设计师按照 iPhone 6 的视觉稿标准出图即可,即宽度为 750px,按照 750px 出图,那么我们写页面时直接使用测量的尺寸来设置 rpx 就行了。

# flex 布局

在 HTML5 标准中,flex 布局可以简便、完整、响应式地实现各种页面布局,小程序作为晚于 flex 标准创建的 Hybrid 解决方案,自然在布局设计上采用了先进的 flex 布局。

关于 flex 布局相关知识,可以参考阮一峰的 flex 布局教程:语法篇 (opens new window)和实例篇 (opens new window)。

# 小结

本节从小程序账号注册说起,依次介绍了小程序开发者工具、小程序开发语言、小程序项目、开发和布局相关的基础知识,内容由浅到深,全面地帮助大家入门小程序开发。

更多微信小程序的基础知识可以参考官方文档:简易教程 (opens new window)、框架 (opens new window)、组件 (opens new window)、API (opens new window)。

阅读全文