小程序

基础

小程序简介

详见:小程序简介

应用程序的结构

小程序结构划分:最上层App -> 多个Page -> 多个组件

应用程序的结构

应用目录的结构

应用目录的结构

详见:小程序代码构成

小程序的MVVM架构

  • Vue的MVVM和小程序MVVM对比

    1577521885598

  • MVVM为什么好用呢?

    • DOM Listeners: ViewModel层可以将DOM的监听绑定到Model层
    • Data Bindings: ViewModel层可以将数据的变量, 响应式的反应到View层
  • MVVM架构将我们从 命令式编程 转移到 声明式编程

    命令式编程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <body>
    <h2 class="title"></h2>
    <button class="btn"></button>
    <input type="date"/>

    <script>
    // 1.定义变量
    let name = "哈哈"

    // 2.通过class获取元素
    const titleEl = document.querySelector('.title')

    // 3.设置内容的显示
    titleEl.textContent = name;

    // 4.监听按钮的dom对象
    const btnEl = document.querySelector('.btn')

    // 5.给按钮绑定点击事件
    btnEl.addEventListener('click', () => {
    name = '呵呵'
    titleEl.textContent = name;
    })
    </script>
    </body>

    声明式编程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <body>
    <div id="app">
    <h2>{{title}}</h2>
    <button @click="btnClick">按钮</button>
    <input type="date"/>
    </div>


    <script src="vue.js"></script>
    <script>
    new Vue({
    el: '#app',
    data: {
    title: '哈哈'
    },
    methods: {
    btnClick() {
    this.title = '呵呵'
    }
    }
    })
    </script>
    </body>

    有上面两段代码对比可知,声明式编程比命令式编程更加简洁,不用再通过获取 dom 对象来进行视图数据的更改、绑定监听事件等。

配置小程序

全局配置

小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。

完整配置项说明请参考小程序全局配置

一下只说明几个最常用的配置选项

属性 类型 必填 描述
pages string[] 页面路径列表
window Object 全局的默认窗口表现
tabBar Object 底部 tab 栏的表现
  • pages: 页面路径列表
    • 用于指定小程序由哪些页面组成,每一项都对应一个页面的 路径(含文件名) 信息。
    • 小程序中所有的页面都是必须在pages中进行注册的。
  • window: 全局的默认窗口展示
    • 用于设置小程序的状态栏、导航条、标题、窗口背景色。
  • tabBar: 底部tab栏的展示
    • 指定 tab 栏的表现,以及 tab 切换时显示的对应页面

以下是一个包含了部分常用配置选项的 app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"pages": [
"pages/home/home",
"pages/category/category"
],
"window": {
"navigationBarBackgroundColor": "#ff5777",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "购物街",
"backgroundColor": "#f00",
"backgroundTextStyle": "dark"
},
"tabBar": {
"selectedColor": "#ff5777",
"list": [{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/assets/images/tabbar/home.png",
"selectedIconPath": "/assets/images/tabbar/home_active.png"
}, {
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "/assets/images/tabbar/category.png",
"selectedIconPath": "/assets/images/tabbar/category_active.png"
}]
}
}

效果图

页面配置

  • 每一个小程序页面也可以使用 .json 文件来对本页面的窗口表现进行配置。

    • 页面中配置项在当前页面会覆盖 app.json 的 window 中相同的配置项。

      覆盖 app.json 的 window 中相同的配置项

      1
      2
      3
      4
      5
      6
      {
      "usingComponents": {},
      "navigationBarBackgroundColor": "#f80",
      "navigationBarTextStyle": "black",
      "navigationBarTitleText": "商品分类"
      }

      页面配置中只能设置 app.jsonwindow 对应的配置项,以决定本页面的窗口表现,所以无需写 window 这个属性。

    • 关于usingComponents选项, 讲到自定义组建时, 再详细讲解

小程序的双线程模型

微信客户端给小程序所提供的环境为宿主环境。宿主环境提供了小程序的双线程模型

  • WXML模块和WXSS样式运行于 渲染层,渲染层使用WebView线程渲染(一个程序有多个页面,会使用多个WebView的线程)。
  • JS脚本(app.js/home.js等)运行于 逻辑层,逻辑层使用JsCore运行JS脚本。
  • 这两个线程都会经由 Native(代指微信客户端)进行中转交互。

双线程模型

界面渲染过程

首先,我们需要知道,wxml等价于一棵DOM树,也可以使用一个JS对象来模拟(虚拟DOM)

虚拟DOM

那么,WXML可以先转成JS对象,再渲染出真正的DOM树

1577542506947

通过setData把msg数据从“Hello World”变成“Goodbye”

  • 产生的JS对象对应的节点就会发生变化
  • 此时可以对比前后两个JS对象得到变化的部分
  • 然后把这个差异应用到原来的Dom树上
  • 从而达到更新UI的目的,这就是“数据驱动”的原理

1577542715013

界面渲染整体流程:

  1. 在渲染层,宿主环境会把WXML转化成对应的JS对象;
  2. 将JS对象再次转成真实DOM树,交由渲染层线程渲染;
  3. 数据变化时,逻辑层提供最新的变化数据,JS对象发生变化比较进行diff算法对比;
  4. 将最新变化的内容反映到真实的DOM树中,更新UI;

小程序的启动流程

通过了解小程序的启动流程,我们就知道了自己代码的执行顺序:

小程序的启动流程

注册小程序

参数解析

详见 APP

App(Object object)

注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。在注册时, 可以绑定对应的生命周期函数, 在生命周期函数中, 执行对应的代码。

App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。

属性 类型 默认值 必填 说明
onLaunch function 生命周期回调——监听小程序初始化。
onShow function 生命周期回调——监听小程序启动或切前台。
onHide function 生命周期回调——监听小程序切后台。
onError function 错误监听函数。
onPageNotFound function 页面不存在监听函数。
其他 any 开发者可以添加任意的函数或数据变量到 Object 参数中,用 this 可以访问

关于小程序前后台的定义和小程序的运行机制,请参考运行机制章节。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// app.js 注册一个小程序实例
App({

/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {

},

/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {

},

/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {

},

/**
* 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
*/
onError: function (msg) {

},
globalData: 'I am global data'
})

onLaunch(Object object)

小程序初始化完成时触发,全局只触发一次。参数也可以使用 wx.getLaunchOptionsSync 获取。

参数:与 wx.getLaunchOptionsSync 一致

onShow(Object object)

小程序启动,或从后台进入前台显示时触发。也可以使用 wx.onAppShow 绑定监听。

参数:与 wx.onAppShow 一致

onHide()

小程序从前台进入后台时触发。也可以使用 wx.onAppHide 绑定监听。

onError(String error)

小程序发生脚本错误或 API 调用报错时触发。也可以使用 wx.onError 绑定监听。

参数:与 wx.onError 一致

onPageNotFound(Object object)

基础库 1.9.90 开始支持,低版本需做兼容处理

小程序要打开的页面不存在时触发。也可以使用 wx.onPageNotFound 绑定监听。注意事项请参考 wx.onPageNotFound

参数:与 wx.onPageNotFound 一致

示例代码:

1
2
3
4
5
6
7
App({
onPageNotFound(res) {
wx.redirectTo({
url: 'pages/...'
}) // 如果是 tabbar 页面,请使用 wx.switchTab
}
})

注册App

注册App时,我们一般会有如下操作

  1. 判断小程序的进入场景
  2. 监听生命周期函数,在生命周期中执行对应的业务逻辑,比如在某个生命周期函数中获取微信用户的信息。
  3. 因为App()实例只有一个,并且是全局共享的(单例对象),所以我们可以将一些共享数据放在这里。
  • 小程序的进入场景

    小程序常见的打开场景:群聊会话中打开、小程序列表中打开、微信扫一扫打开、另一个小程序打开等(场景值列表
    如何确定场景:在onLaunch和onShow生命周期回调函数中,会有options参数,其中有scene值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    App({
    ...
    // 小程序显示出来时
    onShow (options) {
    // 1.判断小程序的进入场景
    switch(options.scene) {
    case 1001: /*do something*/; break;
    case 1005: /*do something*/; break;
    ...
    }
    },
    ...
    })
  • 获取微信用户的基本信息的方式:

    1. wx.getUserInfo即将废弃的接口
    2. button 组件 – 将 open-type 赋值为 getUserInfo,并且绑定 bindgetuserinfo 事件去获取;
    3. 使用 open-data 组件展示用户信息;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 1.wx.getUserInfo (app.js)
    App({
    ...
    // 小程序显示出来时
    onShow (options) {
    // 获取用户信息,并且获取到用户信息之后,将用户信息传递给服务器
    wx.getUserInfo({
    success: function(res) {
    console.log(res)
    /* 结果:{
    errMsg: "getUserInfo:ok",
    rawData: "...",
    userInfo:{...},
    ...*/
    }
    })
    ...
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 2.通过button组件获取用户信息必须要有以下2个步骤 将open-type取值为getUserInfo
    并且绑定bindgetuserinfo事件去获取;
    这时候的回调函数的event对象才有用户信息-->
    <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取授权</button>
    <!-- 在对应的page的js文件中
    Page({
    onGetUserInfo(event) {
    console.log(event)
    }
    })
    -->
    1
    2
    3
    4
    <!-- 3.open-data 展示用户信息 -->
    <open-data type="userNickName"></open-data>
    <open-data type="userGender"></open-data>
    <open-data type="userAuatarUrl"></open-data>
  • 保存全局变量

    1
    2
    3
    4
    5
    6
    App({
    globalData: {
    name: 'vigor',
    age: 18
    }
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 获取全局数据,例如在 home.js 中
    // 1.获取全局的app对象
    const app = getApp()

    Page({
    data: {
    name: 'defaultName',
    age: 0
    },
    onClick() {
    // 2.通过app.globalData属性的方式获取
    this.setData({
    name: app.globalData.name,
    age: app.globalData.age
    })
    }
    })

注册页面

Page(Object object)

注册小程序中的一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。

参数

Object object

属性 类型 默认值 必填 说明
data Object 页面的初始数据
onLoad function 生命周期回调—监听页面加载
onShow function 生命周期回调—监听页面显示
onReady function 生命周期回调—监听页面初次渲染完成
onHide function 生命周期回调—监听页面隐藏
onUnload function 生命周期回调—监听页面卸载
onPullDownRefresh function 监听用户下拉动作
onReachBottom function 页面上拉触底事件的处理函数
onShareAppMessage function 用户点击右上角转发
onPageScroll function 页面滚动触发事件的处理函数
onResize function 页面尺寸改变时触发,详见 响应显示区域变化
onTabItemTap function 当前是 tab 页时,点击 tab 时触发
其他 any 开发者可以添加任意的函数或数据到 Object 参数中,在页面的函数中用 this 可以访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// pages/about/about.js
Page({
/**
* 页面的初始数据
*/
data: {

},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {

},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {

},
/**
* 生命周期函数--监听页面显示。比onReady先触发
*/
onShow: function () {

},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {

},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {

},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {

},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {

},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {

},
// 事件监听
onClick(e) {
console.log('按钮被点击')
}
})

注册一个Page页面时,我们一般有如下操作

  1. 生命周期函数中发送网络请求,从服务器获取数据;
  2. 初始化一些数据,以方便被wxml引用展示;
  3. 监听wxml中的事件,绑定对应的事件函数;
  4. 其他一些监听(比如页面滚动、上拉刷新、下拉加载更多等);

网络请求和其他一些事件的监听,放在后续再来使用

Page实例生命周期

Page实例生命周期

常见内置组件

WXSS

页面样式

三种写法:

  • 内联样式、局部样式、全局样式

  • 三种样式都可以作用于页面的组件

如果有相同的样式,优先级依次是:内联样式 > 局部样式 > 全局样式

内联样式

style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。

1
2
<view style="color: red; font-size: 20px;">内联样式</view>
<view style="color:{{color}};" />

全局样式与局部样式

定义在 app.wxss 中的样式为全局样式,作用于每一个页面。在 page 的 wxss 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器。

样式导入

使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。

示例代码:

1
2
3
4
/**在common文件夹下的 common.wxss **/
.small-p {
padding:5px;
}
1
2
3
4
5
/** app.wxss **/
@import "./common/common.wxss";
.middle-p {
padding:15px;
}

为了减少开发者样式开发的工作量,小程序官方提供了WeUI.wxss基本样式库

选择器

目前支持的选择器有:

选择器 样例 样例描述
.class .intro 选择所有拥有 class=“intro” 的组件
#id #firstname 选择拥有 id=“firstname” 的组件
element view 选择所有 view 组件
element, element view, checkbox 选择所有文档的 view 组件和所有的 checkbox 组件
::after view::after 在 view 组件后边插入内容
::before view::before 在 view 组件前边插入内容
选择器 !important 内联样式 id选择器 类、属性、伪类选择器 元素选择器 继承的样式
权重 最高 1000 100 10 1 没有权重

尺寸单位

  • rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
设备 rpx换算px (屏幕宽度/750) px换算rpx (750/屏幕宽度)
iPhone5 1rpx = 0.42px 1px = 2.34rpx
iPhone6 1rpx = 0.5px 1px = 2rpx
iPhone6 Plus 1rpx = 0.552px 1px = 1.81rpx

建议: 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。

详见WXSS

WXML

详见 WXML语法参考

列表渲染 – key作用

我们看到,使用wx:for时,会报一个警告,这个提示告诉我们,可以添加一个key来提供性能。

  • 为什么需要这个key属性呢(了解):这个其实和小程序内部也使用了虚拟DOM有关系(和Vue、React很相似)。

  • 当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点

    我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的。

    1577687027721

    即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

  • 所以我们需要使用key来给每个节点做一个唯一标识。Diff算法就可以正确的识别此节点。找到正确的位置区插入新的节点。

    1577687133741

key的作用主要是为了高效的更新虚拟DOM

WXS

WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。

WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。

WXS 语法参考

事件处理

详见 事件

touches和changedTouches的区别

  • touchend中不同
  • 多手指触摸时不同

在touchend中不同

多指触摸

组件化开发

组件化

人面对复杂问题的处理方式:

  • 任何一个人处理信息的逻辑能力都是有限的

  • 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。

  • 但是,我们人有一种天生的能力,就是将问题进行拆解。

  • 如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。

组件化也是类似的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。

  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

  • 我们将一个完整的页面分成很多个组件。每个组件都用于实现页
    面的一个功能块。而每一个组件又可以进行细分。

    1577778532723

详见 自定义组件

组件模板和样式

组件模板和样式

组件的样式细节

  1. 组件内的样式外部样式 的影响
    • 组件内的class样式,只对组件wxml内的节点生效, 对于引用组件的Page页面不生效。
    • 组件内不能使用id选择器属性选择器标签选择器(如果使用这些选择器的话,还是会生效,只是会有警告)
  2. 外部样式 对 组件内样式 的影响
    • 外部使用class的样式,只对外部wxml的class生效,对组件内是不生效的
    • 外部使用了id选择器、属性选择器不会对组件内产生影响
    • 外部使用了标签选择器,会对组件内产生影响

结论:组件内的class样式和组件外的class样式, 默认是有一个隔离效果的;为了防止样式的错乱,官方不推荐使用id、属性、标签选择器;

组件间通信与事件

组件间的基本通信方式有以下几种。

  • WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 2.0.9 开始,还可以在数据中包含函数)。具体在 组件模板和样式 章节中介绍。
  • 事件:用于子组件向父组件传递数据,可以传递任意数据。
  • 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。

很多情况下,组件内展示的内容(数据、样式、标签),并不是在组件内写死的,而且可以由使用者来决定。

1577783664693

向组件传递数据 - properties

给组件传递数据

  • 大部分情况下,组件只负责布局和样式,内容是由使用组件的对象决定的。

  • 所以,我们经常需要从外部传递数据给我们的组件,让我们的组件来进行展示。方法是使用properties属性:

支持的类型:String、Number、Boolean、Object、Array、null(不限制类型)

向组件传递样式 - externalClasses

给组件传递样式

有时候,我们不希望将样式在组件内固定不变,而是外部可以决定样式。这个时候,我们可以使用externalClasses属性:

  1. 在Component对象中,定义externalClasses属性

  2. 在组件内的wxml中使用externalClasses属性中的class

  3. 在页面中传入对应的class,并且给这个class设置样式

组件向外传递事件 – 自定义事件

有时候是自定义组件内部发生了事件,需要告知使用者,这个时候可以使用自定义事件

详见组件间通信与事件

页面直接调用组件方法

页面使用自定义组件时会多一些方法,如, selectComponent 。selectComponent 可以获取子组件实例对象,因此页面可以获取自定义组件的方法

1
2
this.selectComponent('#idName/.className')
// 参数为自定义组件的id名或者类名

在home页面内使用自定义组件 my-cpn

1
2
3
<!--pages/home/home.wxml-->
<button bindtap="handleChange">修改my-cpn组件内的数据</button>
<my-cpn id="myCpn"/>
1
2
3
4
5
6
7
// pages/home/home.js
Page({
handleChange(e) {
const myCpn = this.selectComponent('#myCpn')
myCpn.increment(10)
}
})

自定义组件 my-cpn

1
2
<!--components/my-cpn.wxml-->
<view>{{count}}</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// components/my-cpn.js
Component({
data: {
count: 0
},
/**
* 组件的方法列表
*/
methods: {
// 改变 count
increment(num) {
this.setData({ count: this.data.count + num })
}
}
})

插槽

  • slot翻译为插槽:
    • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
    • 插槽的目的是让我们原来的设备具备更多的扩展性。
    • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
  • 组件的插槽:
    • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
    • 让使用者可以决定组件内部的一些内容到底展示什么。
  • 例子:移动网站中的导航栏。
    • 移动开发中,几乎每个页面都有导航栏。
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件。
    • 一旦有了这个组件,我们就可以在多个页面中复用了。
    • 但是,每个页面的导航是一样的吗?类似下图

1577862185588

单个插槽的使用

除了内容和样式可能由外界决定之外,也可能外界想决定显示的方式
比如我们有一个组件定义了头部和尾部,但是中间的内容可能是一段文字,也可能是一张图片,或者是一个进
度条。在不确定外界想插入什么其他组件的前提下,我们可以在组件内预留插槽:

1
2
3
4
5
6
<!--components/slot-cpn/slot-cpn.wxml-->
<view class="slot-cpn">
<view class="header">我是头部</view>
<slot></slot>
<view class="footer">我是尾部</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--pages/home/home.wxml-->
<text>pages/home/home.wxml</text>
<!-- 1.插入一段文字 -->
<slot-cpn>
<text>我是一段文字</text>
</slot-cpn>

<!-- 2.插入一个按钮 -->
<slot-cpn>
<button>按钮</button>
</slot-cpn>

<!-- 3.插入一张图片 -->
<slot-cpn>
<image src="https://baidu.com" />
</slot-cpn>

多个插槽的使用

在组件的wxml中可以包含 slot 节点,用于承载组件使用者提供的wxml结构。

默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用。

1
2
3
4
5
6
7
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: { /* ... */ },
methods: { /* ... */ }
})

此时,可以在这个组件的wxml中使用多个slot,以不同的 name 来区分。

1
2
3
4
5
6
<!-- 组件模板 -->
<view class="wrapper">
<slot name="before"></slot>
<view>这里是组件的内部细节</view>
<slot name="after"></slot>
</view>

使用时,用 slot 属性来将节点插入到不同的slot上。

1
2
3
4
5
6
7
8
9
<!-- 引用组件的页面模板 -->
<view>
<component-tag-name>
<!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 -->
<view slot="before">这里是插入到组件slot name="before"中的内容</view>
<!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 -->
<view slot="after">这里是插入到组件slot name="after"中的内容</view>
</component-tag-name>
</view>

Component构造器

详见 Component 构造器Component 参考文档

Component构造器用户创建我们的自定义组件对象, 调用Component时, 可以传入属性、数据、方法等

Component构造器

Component构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// components/my-cpn/my-cpn.js
Component({
// -----组件的属性列表-----
properties: {
title: {
type: String,
value: '',
observer: function(newVal, oldVal) {
// 监听 title 的改变
}
}
},

// -----组件的初始数据-----
data: {
counter: 0
},

// -----组件的方法列表-----
methods: {
foo() {

}
},

// -----定义组件的配置选项-----
// multipleSlots: 在使用多插槽时需要设置true
// styleIsolation:设置样式的隔离方式
options: {
multipleSlots: true
},

// -----外界给组件传入额外的样式-----
externalClasses: ['my-class', 'you-class'],

// -----可以监听properties/data的改变-----
observers: { // 与observer相比,没有oldVal这个旧值参数
counter: function(newVal) {
console.log(newVal)
}
},

//-----组件中监听声明周期函数-----
// 1.监听所在页面的生命周期
pageLifetimes: {
show() {
console.log('监听组件所在页面显示出来时')
},
hide() {
console.log('监听组件所在页面隐藏起来时')
},
resize() {
console.log('监听页面尺寸的改变')
}
},

// 2.监听组件本身的生命周期
lifetimes: {
created() {
console.log('组件被创建出来')
},
attached() {
console.log('组件被添加到页面')
},
ready() {
console.log('组件被渲染出来')
},
moved() {
console.log('组件被移动到另外一个节点')
},
detached() {
console.log('组件被移除掉')
}
}
})

小程序系统API

网络请求

基本使用

微信提供了专属的API接口,用于网络请求: wx.request(Object object)

属性 类型 默认值 必填 说明 最低版本
url string 开发者服务器接口地址
data string/object/ArrayBuffer 请求的参数
header Object 设置请求的 header,header 中不能设置 Referer。 content-type 默认为 application/json
method string GET HTTP 请求方法
dataType string json 返回的数据格式
responseType string text 响应的数据类型 1.7.0
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Page({
onLoad: function(options) {
// -----原生的方式发送网路请求------
// 1.发送最简单的get请求
wx.request({
url: 'http://httpbin.org/get',
success: function (res) {
console.log(res)
},
fail: function(err) {
console.log(err)
}
}),
// 2.发送get请求,并且携带参数
wx.request({
url: 'http://httpbin.org/get',
data: {
type: 'sell',
page: 1
},
success: function(res) {
console.log(res)
}
}),
// 3.发送post请求,并且携带参数
wx.request({
url: 'http://httpbin.org/post',
method: 'post',
data: {
name: 'vigor',
age: 12
},
success: function (res) {
console.log('post请求', res)
}
})
}
}

网络请求的封装

  1. 降低网络请求和 wx.request 的耦合度

  2. 使用 Promise 的方法获取回调结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// /servcie/request.js
export default function request(options) {
const {
url = '',
data = '',
header = {},
method = 'GET',
dataType = 'json',
responseType = 'text'
} = options;
return new Promise((resolve, reject) => {
wx.request({
url: url,
data: data,
header: header,
method: method,
dataType: dataType,
responseType: responseType,
success: resolve,
fail: reject,
complete: function(res) {},
})
})
}
1
2
3
4
5
6
7
8
9
10
11
12
// pages/home/home.js
// 使用请求的封装
import request from '../../service/request.js'
request({
url: 'http://httpbin.org/post',
method: 'post',
data: {
name: 'vigor',
age: 12
}
}).then(value => console.log('请求成功', value))
.catch(err => console.log('请求失败', err))

展示弹窗

小程序中展示弹窗有四种方式:

页面分享

分享是小程序扩散的一种重要方式,小程序中有两种分享方式:

  1. 点击右上角的菜单按钮,之后点击转发

  2. 点击某一个按钮,直接转发

    1
    <button open-type="share">分享按钮</button>

当我们转发给好友一个小程序时,通常小程序中会显示一些信息,可以通过 Page()里面的回调函数 onShareAppMessage 决定这些信息的展示

onShareAppMessage(Object object)

监听用户点击页面内转发按钮(button 组件 open-type="share")或右上角菜单“转发”按钮的行为,并自定义转发内容。

注意:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮

参数 Object object:

参数 类型 说明 最低版本
from String 转发事件来源。 button:页面内转发按钮; menu:右上角转发菜单 1.2.4
target Object 如果 from 值是 button,则 target 是触发这次转发事件的 button,否则为 undefined 1.2.4
webViewUrl String 页面中包含web-view组件时,返回当前web-view的url 1.6.4

此事件处理函数需要 return 一个 Object,用于自定义转发内容,返回内容如下:

自定义转发内容 基础库 2.8.1 起,分享图支持云图片。

字段 说明 默认值 最低版本
title 转发标题 当前小程序名称
path 转发路径 当前页面 path ,必须是以 / 开头的完整路径
imageUrl 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4。 使用默认截图 1.5.0

示例代码

在开发者工具中预览效果

1
2
3
4
5
6
7
8
9
10
11
12
Page({
onShareAppMessage: function (res) {
if (res.from === 'button') {
// 来自页面内转发按钮
console.log(res.target)
}
return {
title: '自定义转发标题',
path: '/page/user?id=123'
}
}
})

小程序登录

登录流程

  1. 调用wx.login获取code
  2. 调用wx.request发送code到我们自己的服务器(我们自己的服务器会返回一个登录态的标识,比如token)
  3. 将登录态的标识token进行存储,以便下次使用
  4. 请求需要登录态标识的接口时,携带token

小程序登录流程

说明:

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID会话密钥 session_key

之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

注意:

  1. 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥
  2. 临时登录凭证 code 只能使用一次

小程序的登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// app.js
const TOKEN = 'token'

App({
// 对象: 小程序关闭掉
globalData: {
token: ''
},
onLaunch: function () {
// 1.先从缓冲中取出token
const token = wx.getStorageSync(TOKEN)

// 2.判断token是否有值
if (token && token.length !== 0) { // 已经有token,验证token是否过期
this.check_token(token) // 验证token是否过期
} else { // 没有token, 进行登录操作
this.login()
}
},
check_token(token) {
console.log('执行了验证token操作')
wx.request({
url: 'http://123.207.32.32:3000/auth',
method: 'post',
header: {
token
},
success: (res) => {
if (!res.data.errCode) {
console.log('token有效')
this.globalData.token = token;
} else {
this.login()
}
},
fail: function(err) {
console.log(err)
}
})
},
login() {
console.log('执行了登录操作')
wx.login({
// code只有5分钟的有效期
success: (res) => {
// 1.获取code
const code = res.code;

// 2.将code发送给我们的服务器
wx.request({
url: 'http://123.207.32.32:3000/login',
method: 'post',
data: {
code
},
success: (res) => {
// 1.取出token
const token = res.data.token;

// 2.将token保存在globalData中
this.globalData.token = token;

// 3.进行本地存储
wx.setStorageSync(TOKEN, token)
}
})
}
})
}
})

请求 token 接口

接口地址:/login

请求方式:post

参数列表:code

返回值:token

验证 token 接口

接口地址:/auth

请求方式:post

参数列表:header,token:本地保存的 token

返回值:

错误码:

1001:没有传入 token

1002:传入错误的 token

1003:token 过期

界面跳转

界面的跳转有两种方式:通过navigator组件 和 通过wx的API跳转

  • navigator组件主要就是用于界面的跳转的:(详见 navigator)

    属性 类型 默认值 必填 说明 最低版本
    target string self 在哪个目标上发生跳转,默认当前小程序 2.0.7
    url string 当前小程序内的跳转链接 1.0.0
    open-type string navigate 跳转方式 1.0.0
    delta number 1 当 open-type 为 ‘navigateBack’ 时有效,表示回退的层数 1.0.0
  • open-type有如下取值:

    说明 最低版本
    navigate 对应 wx.navigateTowx.navigateToMiniProgram 的功能
    redirect 对应 wx.redirectTo 的功能
    switchTab 对应 wx.switchTab 的功能
    reLaunch 对应 wx.reLaunch 的功能 1.1.0
    navigateBack 对应 wx.navigateBack 的功能 1.1.0
    exit 退出小程序,target="miniProgram"时生效 2.1.0
    • redirect:关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面,并且不能返回。(不是一个压栈)
    • switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。(需要在tabBar中定义的)
    • reLaunch:关闭所有的页面,打开应用中某个页面。(直接展示某个页面,并且可以跳转到tabBar页面)
1
2
3
4
<!--页面跳转-->
<navigator url='/pages/detail/detail' open-type='redirect'>
跳到详情页(redirect)
</navigator>

导航返回

导航返回有两个属性来起作用:

  • open-type:navigateBack(表示该navigator组件用于返回)
  • delta:返回的层级(指定返回的层级,open-type必须是navigateBack才生效)
1
<navigator open-type="navigateBack" delta="2">放回上两级</navigator>

数据传递

传递方式分析

如何在界面跳转过程中我们需要相互传递一些数据,应该如何完成呢?

  • 首页 -> 详情页:使用URL中的query字段
  • 详情页 -> 首页:在详情页内部拿到首页的页面对象,直接修改数据

数据传递方式

传递过程

  • 首页 -> 详情页:通过修改URL传递参数
  • 详情页 -> 首页:返回时携带数据有两个问题需要考虑
    • 问题一: 在什么地方修改数据
      • 如果你是监听按钮或者navigator的点击来返回时, 可以通过bindtap来映射到某个函数, 在函数中完成.
      • 但是这种方式不能监听左上角返回按钮的点击.
      • 所以我们选择在onUnload中修改数据
    • 问题二: 如何修改数据
      • 小程序并没有提供直接修改数据的方法.
      • 但是可以通过getCurrentPages来获取所有的页面, 然后使用页面对象的setData({})函数来修改

首页通过修改URL传递参数(数据)给详情页

1
2
3
<!--pages/home/home.wxml-->
<!-- 跳转过程中数据的传递 -->
<navigator url='/pages/detail/detail?name=vigor&age=18&height=1.88'>跳到详情页</navigator>
1
2
3
4
5
6
7
// pages/detail/detail.js
// 获取首页通过url传递过来的数据
Page({
onLoad: function (options) {
console.log(options) // {name: "vigor", age: "18", height: "1.88"}
}
})

详情页传递数据给首页

详情页

1
2
<!--pages/detail/detail.wxml-->
<navigator open-type='navigateBack'>返回</navigator>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pages/detail/detail.js
Page({
onUnload() {
// 1.获取首页的页面对象
// getCurrentPages当前所有栈的页面
const pages = getCurrentPages()
const home = pages[pages.length - 2]

// 2.调用页面对象的setData
home.setData({
title: '呵呵呵'
})
}
})

首页

1
2
<!--pages/home/home.wxml-->
<view>{{title}}</view>
1
2
3
4
5
6
7
8
9
10
// pages/home/home.js
Page({

/**
* 页面的初始数据
*/
data: {
title: '哈哈哈'
}
})

当在详情页中点击 “返回” 时,首页的 “title” 由 “哈哈哈” 变为 “呵呵呵”

通过 wx 的api的跳转和返回

很多情况下,我们并不喜欢使用navigator组件来进行跳转,可能我们希望用户点击了某个button或者view时,对该button或者view进行监听,之后,通过相关的代码逻辑实现跳转。
对此,微信也提供了对应的API接口:

  • wx.navigateTo(url[, ])
  • wx.navigateBack([delta])

通过 wx.navigateTo 跳转到详情页

1
2
3
<!--pages/home/home.wxml-->
<!-- 通过代码进行页面跳转 -->
<button size='mini' bind:tap="handlePushDetail">跳到详情页</button>
1
2
3
4
5
6
7
8
9
10
11
12
// pages/home/home.js
Page({
handlePushDetail() {
wx.navigateTo({
url: '/pages/detail/detail?name=Mike',
})

// wx.redirectTo({
// url: '',
// })
}
})

通过 wx.navigateBack 返回上一级页面

1
2
<!--pages/detail/detail.wxml-->
<button size='mini' bind:tap="handleBackHome">返回</button>
1
2
3
4
5
6
7
8
// pages/detail/detail.js
Page({
handleBackHome() {
wx.navigateBack({
delta: 1
})
}
})
-------文章到此结束  感谢您的阅读-------