微信小程序开发之页面间的通信

微信小程序开发之页面间的通信

2019-03-28 | |

在小程序开发过程中,页面与页面之间通信的问题一直都是一个非常重要的问题。而 数据的传递事件的回调 是通信的两种表现形式。

这里总结了几种传值与回调的方式。

注:以下统一使用 A 页面代表前一个页面,B 页面代表后一个页面
博文中引用的 Demo 已经放到 github 上面了,有需要的小伙伴可以去下载

正向传值

正向传值是软件开发中页面与页面之间最常见的一种数据传递的方式。例如当 App 从列表跳转到详情页面的时候,就需要将对应的列表 id 传递给详情页面。

URL传值

简单参数

对于 数值类型字符串类型 的参数,可以直接将参数拼接在 A 页面的 url 路径后面,使用 ? 来分隔路径与参数,参数名与参数值之间使用 = 连接,多个参数之间用 & 隔开,如 path?key=value&key2=value2 ,然后在 B 页面的 onLoad 方法里面来取值。

A 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Page({

/// 略...

/**
* 跳转到 B 页面
*/
jumpToPageB() {
/// 将 name 参数传递给 B 页面
wx.navigateTo({
url: '../B/B?name=简单参数',
});
}
})

B 页面

1
2
3
4
5
6
Page({
onLoad: function (options) {
/// 接收 A 页面传递过来的 name 参数
const name = options.name;
}
})
复杂参数

对于 数组对象 这样的参数,它的传值方式同样是将参数拼接在路径后面,只不过需要先将参数值转化为 json 字符串。

A 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Page({

/// 略...

/**
* 跳转到 B 页面
*/
jumpToPageB() {
const params = [{
id: 1,
name: '复杂参数'
}, {
id: 2,
name: '复杂参数'
}];

/// 将 params 参数传递给 B 页面
/// 需要将 params 转化为 json 字符串
wx.navigateTo({
url: '../B/B?params=' + JSON.stringify(params),
});
}
})

B 页面

1
2
3
4
5
6
7
Page({
onLoad: function (options) {
/// 接收 A 页面传递过来的 params 参数
/// 传递过来的参数是一个 json 字符串,可以将它转化为对象
const params = JSON.parse(options.params);
}
})

注意:通过 URL 这种方式进行的数据传递只支持 wx.navigateTowx.redirectTo 以及 wx.reLaunch 这三种跳转形式,如果你是通过 wx.switchTab 来进行的跳转则不能通过这种方式进行参数传递。

全局的App对象传值

通过获取 App 对象的实例,将需要的参数绑定在这个实例上面,在 A 页面进行赋值,B 页面取值,也可以达到传值的目的。这种方式利用了全局的 App 对象,通过 getApp() 方法获取的 App 的实例永远都是同一个(可以理解为一个单例对象),因此不同页面直接可以通过 App 实例下的属性来共享数据。

A页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// 获取全局 App 实例
const app = getApp();

Page({

/// 略....

/**
* 跳转到 B 页面
*/
jumpToPageB() {
/// 页面跳转之前,先将参数 globalData 绑定在 app 对象上
app.globalData = {
id: 1,
name: '全局参数'
};

/// 页面跳转
wx.navigateTo({
url: '../B/B'
})
}
})

B 页面

1
2
3
4
5
6
7
8
9
10
/// 获取全局 App 实例
const app = getApp();

Page({

onLoad: function (options) {
/// 取出绑定在 app 对象上的 globalData 属性
const globalData = app.globalData;
}
})

注意:由于获取到的 App 对象是一个全局的对象,当使用这种方式进行传值时,我们所定义的那些字段都会被当作这个对象的一个属性添加在它上面,因此对于那些数据量比较大,并且又不是大部分页面所需要的数据(仅仅只是从 A 页面到 B 页面进行的数据传递)就不建议使用这种方式。一般我们只是将一些数据量不大,并且很多页面需要使用到的数据通过这种方式进行传递,如:用户的登录状态、用户的id等。

本地存储传值

微信提供了一组与数据缓存相关的 API,可以将需要传递的数据缓存在本地,然后在需要用到的时候从本地取出来。通过这种方式也可以进行一些页面之间数据的传递。

A 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Page({

/// 略....

/**
* 跳转到 B 页面
*/
jumpToPageB() {
const localData = {
id: 1,
name: '本地数据'
};

/// 将数据缓存在本地,并指定一个名称 LOCAL_DATA
wx.setStorageSync('LOCAL_DATA', localData);

/// 跳转到 B 页面
wx.navigateTo({
url: '../B/B',
});
}
})

B 页面

1
2
3
4
5
6
Page({
onLoad: function (options) {
/// 通过 LOCAL_DATA 取出缓存在本地的数据
const localData = wx.getStorageSync('LOCAL_DATA');
}
})

注意:微信官方文档已经明确说明,同一个微信用户,同一个小程序 storage 上限为 10MB。如果用户储存空间不足,会清空最近最久未使用的小程序的本地缓存。因此不建议将关键信息全部存在 storage,以防储存空间不足或用户换设备的情况。我们一般只是将少量的数量缓存在本地,如:登录相关的信息。

逆向传值与回调

日常开发中也经常会用到这种情况,当我们从 A 页面跳转到 B 页面,在 B 页面做了某些操作后,需要通知 A 页面,并回传相应的数据给 A 页面。比如:B 页面是一个选择联系人的页面,当我们选择完联系人后,需要将选好的那些联系人的数据传回给 A 页面。

全局的App对象传值

和正向传值一样,通过全局的 App 对象这种方式进行的数据传递也可用于逆向传值,也就是数据从 B 页面传递回 A 页面。具体使用方式和正向传值基本一致,只不过现在是在 B 页面进行赋值,在 A 页面进行取值,将上面的存取操作返回过来而已。

A页面

1
2
3
4
5
6
7
8
9
10
11
/// 获取全局 App 实例
const app = getApp();

Page({

onShow: function (options) {
/// 取出绑定在 app 对象上的 globalData 属性
/// 如果想要在 B 页面返回 A 页面的时候就获取到传递过来的数据,就需要在 onShow 方法里面来取值
const globalData = app.globalData;
}
})

B 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// 获取全局 App 实例
const app = getApp();

Page({

/// 略...

/**
* 返回到到 A 页面
*/
backToPageA() {
/// 页面返回之前,先将参数 globalData 绑定在 app 对象上
app.globalData = {
id: 1,
name: '全局参数'
};

/// 返回 A 页面
wx.navigateBack();
}
})

本地存储传值

同正向传值一样,可以将需要传递的数据缓存在本地,然后在需要用到的地方从本地取出来。

A页面

1
2
3
4
5
6
7
8
9
10
11
/// 获取全局 App 实例
const app = getApp();

Page({

onShow: function (options) {
/// 通过 LOCAL_DATA 取出缓存在本地的数据
/// 如果想要在 B 页面返回 A 页面的时候就获取到传递过来的数据,就需要在 onShow 方法里面来取值
const localData = wx.getStorageSync('LOCAL_DATA');
}
})

B 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// 获取全局 App 实例
const app = getApp();

Page({

/// 略...

/**
* 返回到到 A 页面
*/
backToPageA() {
const localData = {
id: 1,
name: '本地数据'
};

/// 将数据缓存在本地,并指定一个名称 LOCAL_DATA
wx.setStorageSync('LOCAL_DATA', localData);

/// 返回 A 页面
wx.navigateBack();
}
})

获取Page实例传值

我们可以在当前页面获取前一个页面的 Page 实例,就可以操作这个实例,进而达到传值的目的。
getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。

A页面

1
2
3
4
5
6
Page({

data: {
id: ''
},
})

B 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// 获取全局 App 实例
const app = getApp();

Page({

/// 略...

/**
* 返回到到 A 页面
*/
backToPageA() {
// 获取页面栈的数组
const pages = getCurrentPages();
// 取出上一级页面
const prePage = pages[pages.length - 2];
// 直接赋值
prePage.setData({
id: '1'
});

/// 返回 A 页面
wx.navigateBack();
}
})

注意:使用这种方式进行传值有一个前提,这两个通信的页面之间需要处于同一个页面栈。也就是说这种方式只适用于通过 wx.navigateTo 来进行的页面跳转。

事件回调传值

事件回调也可以理解为事件的订阅与分发,它应该是平时开发过程中最常用的一种反向传值的处理方式。做过移动端开发小伙伴应该对这个非常熟悉,类似于 iOS 的通知以及 Andriod 的广播。
这里介绍一个第三方的 JavaScript 库 onfire.js

使用步骤
  1. 导入 onfire.js 文件,然后在项目的公共模块中创建一个 onfire 实例,用于数据通信。比如 util.js 中。

    util.js 文件

    1
    2
    3
    4
    5
    6
    import OnFire from 'onfire.js';
    const onFire = new OnFire();

    module.exports = {
    onFire: onFire
    }
  2. A 页面引入 onfire 实例,然后订阅一个事件,并定义好收到回调后的处理方法。在 A 页面的 onUnload 方法中(也就是 A 页面卸载的时候),取消订阅的事件。

    A 页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const tool = require('../../../utils/util.js');
    const onfire = tool.onFire;

    Page({

    onUnload() {
    /// 取消订阅的事件
    onfire.off('Notification');
    },

    onLoad(options) {
    /// 订阅一个事件
    // Notification:事件名称,可以随意命名,但需要于 B 页面的事件名称相同
    // e:传递过来的参数
    onfire.on('Notification', e => {
    // 在这里编写收到回调后的逻辑
    // 略...
    }
    }
    })
  3. B 页面引入 onfire 实例,然后在需要传值或回调的地方发送消息并根据需要传递相应的参数。

    B 页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const tool = require('../../../utils/util.js');
    const onfire = tool.onFire;

    Page({
    /// 略...

    /**
    * 返回到到 A 页面
    */
    backToPageA() {
    /// 分发事件
    // Notification:事件名称,可以随意命名,但需要于 A 页面的事件名称相同
    // 100:传递过去的参数
    onfire.fire('Notification', '100');

    /// 返回 A 页面
    wx.navigateBack();
    }
    })
注意事项
  1. 由于通信双方是通过 onfire 实例来管理消息的订阅与分发的,所以必须保证这两个通信的页面所引用的 onfire 实例是同一个实例,这也是为什么我们需要在一个公共模块中创建 onfire 实例。
  2. 事件的订阅与分发有严格的先后顺序。必须先订阅事件,然后才能进行事件的分发。也就是说在事件分发的时候,必须保证订阅该事件的页面是已经存在的(B 页面在发送消息的时候,A 页面是已经存在的)。这也就解释了这种机制只适用于逆向的传值与回调,而不是正向的。