详解 | 小程序页面间如何进行传递数据
工作中我们经常会遇到B页面需要A页面内的部分或全部数据;C页面内的一个函数执行完之后需要改变B页面内的显示样式;也或者是A和B两个页面用到了同样的网络数据,在其中一个页面做出修改后另一个页面也要随之改变以保证回传服务器时数据的准确性,等等诸如此类的页面间数据传递的问题。
在小程序中组件与组件之间的通信是通过在引用组件处,在自定义组件上添加自定义属性实现的,子组件内部通过properties
进行接收
那页面与页面之间又如何传递数据的呢?
1
页面间URL传值
在小程序中当中,在父页面,通过url
方式传递参数到子页面,是一种比较常见的做法
如下示例所示:应用场景
- 点击列表页面,进入详情页
- 动态改变详情页面的
navBar
中的title
比如
wx.navigateTo({
url: '../detail/detail?cid='+cid+'&token='+token
})
这里面直接通过跳转页面的URL进行传值,然后在另一个页面进行接收
.onLoad: function (opt) {
console.log('cid =' + opt.cid);
console.log('access_token =' + opt.access_token);
}
传递字符串
var model = this.data.str;
wx.navigateTo({ url: '../detail/detail?model=' + model,
})
在下个页面的onload中获取,
onLoad: function (options) {
var bean = options.model;
console.log(options.model)
this.setData({
model:bean
})
}
传递对象
通过提供的JSON.stingify方法,将对象转换成字符串后传递
var model = JSON.stringify( model);
wx.navigateTo({
url: '../detail/detail?model=' + model,
})
//接收
onLoad: function (options) {
//将字符串转换成对象
var bean = JSON.parse(options.model);
}
父页面实例代码
<view>
<view class="list-wrap">
<block wx:for="{{listDatas}}" wx:key="index">
<view bindtap="onListTap" data-list="{{item}}">
<text>{{ item.list_text}}</text>
</view>
</block>
</view>
</view>
css代码
.list-wrap {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 15px 15px;
}
.list-wrap view {
width: 30%;
height: rpx;
border: 1px solid #ccc;
margin-bottom: 15px;
text-align: center;
line-height: rpx;
font-size: rpx;
}
js代码
Page({
/**
* 页面的初始数据
*/
data: {
listDatas: [
{
listId: '1',
list_text: '建钢构混泥房',
link_phone: '137-0113-4148',
linker: '王经理',
},
{
listId: '2',
list_text: '建办公楼房',
link_phone: '137-0113-4148',
linker: '陈经理',
},
{
listId: '3',
list_text: '建冰场钢结构',
link_phone: '137-0113-4148',
linker: '张经理',
},
],
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {},
onListTap(event) {
const {
listId,
list_text,
link_phone,
linker,
} = event.currentTarget.dataset.list;
// 1. 传递参数-通过url的方式传递当前页面数据到子页面当中去,在子页面的onload的options中可以拿到
wx.navigateTo({
url: `/pages/listDetail/listDetail?id=${listId}&navtitle=${list_text}&phone=${link_phone}&link=${linker}`,
});
},
});
切换tab
选项就可以查看对应的代码,在上面示例中,从一个页面跳转到另一个页面是使用wx.navigateTo()
这个方法,如果想要将该页面的数据传递到子页面中,可以通过url
拼接参数的方式进行传递,多个参数之间使用&
符号相连
路径后可以带参数,参数与路径之间使用 ?
分隔,参数键与参数值用 =
相连,不同参数用 &
分隔;如path?key=value&key2=value2
子页面实例代码
<view>
<view class="container">
<view>项目:<text>{{id}}-{{text}}</text></view>
<view>联系人: {{link}}</view>
<view>联系电话: {{phoneNumber}}</view>
</view>
</view>
css代码
.container {
padding: 20px 10px 30px;
}
.container view {
line-height: 30px;
}
js逻辑代码
Page({
/**
* 页面的初始数据
*/
data: {
// 页面中要渲染的数据,数据初始化
id: null,
text: '',
phoneNumber: '',
linker: '',
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
console.log(options);
const { id, navtitle, phone, link } = options;
this._setNavTitle(navtitle);
this._getList(id, navtitle, phone, link);
},
// 设置navTitle
_setNavTitle(navtitle) {
wx.setNavigationBarTitle({
title: navtitle,
});
},
_getList(id, navtitle, phone, link) {
// 改变页面中的数据,setData
this.setData({
text: navtitle,
id,
phoneNumber: phone,
link,
});
},
});
当父页面通过url
的方式传递数据给子页面时,在子页面中的生命周期onLoad
函数中的options
中可以拿到 想要更改什么数据,直接重新setData
就可以了的
注意
url
的方式适合页面间跳转携带参数,多个参数之间使用&
符号拼接- 此方法有一定的局限性,不适宜传入复杂的数据,例如:数组,对象
- 适合参数比较少的情况
url 中有多个参数时传递
在小程序中,向跳转的目标url页面传递的参数有时候远不止一个,使用wx.navigator进行跳转,支持/pages/xxx/xxx?param1=Misplaced &
若url
参数是数组情况
wx.navigateTo({
url: `/pages/listDetail/listDetail?list=${[
listId,
list_text,
link_phone,
linker,
]}`,
});
子页面(跳转目标页)
onLoad: function (options) {
console.log(options);
const list = options.list.split(','); // 通过split分割成数组
console.log(list);
},
分析
当被跳转的 url 中的参数是数组时,那么在跳转的目标页面中的onLoad
生命周期函数的option
,将得到父页面中的字符串参数。通过split
方法将字符串分割为数组,然后通过数组下标的方式拿到对应的参数
父页面中
const name = 'itclanCoder';
const sex = 'boy';
wx.navigateTo({
url: `/pages/listDetail/listDetail?data=${[name, sex]}`,
});
子页面中
onLoad: function (options) {
console.log(options);
const data = options.data.split(','); // 通过split分割成数组
console.log(data); // ["itclanCoder", "boy"]
},
若url
参数是对象情况
在url
参数是对象时,并不会像数组一样,在目标页面中onLoad
的options
对象中是一个字符串,而却是一个对象。我们需要借助JSON.stringify()
对传入的参数对象进行序列化
父页面(对象参数序列化)
wx.navigateTo({
url: `/pages/listDetail/listDetail?obj=${JSON.stringify({
id: listId,
text: list_text,
phone: link_phone,
link: linker,
})}`,
});
通常,我们把参数对象,定义成一个对象的,简化我们的代码,用一个变量对象临时存储的
const params = {
// 参数放到外面,让代码更加清晰,可读,可维护性更高
id: listId,
text: list_text,
phone: link_phone,
link: linker,
};
wx.navigateTo({
url: `/pages/listDetail/listDetail?obj=${JSON.stringify(params)}`,
});
那么在子页面中,需要通过JSON.parse()
对父页面中传递过来的参数进行反序列化,否则拿到的将是字符串对象,是无法通过对象.的方式访问属性
子页面(对象参数反序列化)
onLoad: function (options) {
console.log(options);
const obj = JSON.parse(options.obj); // 将字符串对象转化为真正的对象
console.log(obj); // {id: "1", text: "建钢构混泥房", phone: "137-0113-4148", link: "王经理"}
},
分析:在父页面中若跳转目标的 url 参数是对象的情况下,需要先将参数通过JSON.stringify()
序列化才可以
const params = {
// 参数放到外面,让代码更加清晰,可读,可维护性更高
id: ,
name: '川川',
sex: 'boy',
};
wx.navigateTo({
url: `/pages/listDetail/listDetail?obj=${JSON.stringify(params)}`,
});
那么在子页面(目标页面中)的onLoad
的options
中
onLoad: function (options) {
console.log(options);
const obj = JSON.parse(options.obj); // 将字符串对象转化为真正的对象
console.log(obj); // {id: 22, name: "川川",sex: "boy"}
},
父(上个)页面编码
const params = {
// 参数放到外面,让代码更加清晰,可读,可维护性更高
id: ,
name: '川川',
sex: 'boy',
};
const param = encodeURIComponent(JSON.stringify(params)); // 通过encodeURIComponent编码
wx.navigateTo({
url: `/pages/listDetail/listDetail?obj=${param}`,
});
子页面解码
onLoad: function (options) {
console.log(options);
const tempObj = decodeURIComponent(options.obj)
const obj = JSON.parse(tempObj); // 将字符串对象转化为真正的对象
console.log(obj); // {id: 22, name: "川川",sex: "boy"}
},
注意
- 当父页面传递的
url
参数为对象时,在子页面是无法直接获取的,在父页面中,必须先使用JSON.stringify()
转换为字符串 然后在下个页面使用JSON.parse()
还原为对象,这样在子页面中便可以通过对象的方式拿到 - 当父页面传递的
url
对象数据中含有特殊字符串时,在子页面使用JSON.parse()
还原为对象时会报错。需要在上个(父)页面将对象转化为字符串后(JSON.stringify()
),在使用encodeURIComponent
进行编码,然后在下个(子)页面先用decodeURIComponent
进行解码在还原(JSON.parse()
)为对象。
2
如何返回上一级页面-并刷新页面
在使用wx.navigateTo()
API 进行跳转时,在子页面中可以通过wx.navigateBack()
返回上一级页面的,这个场景在日常开发中,就有不少。例如:写完微博,发完微博成功后,自动要返回到首页,申请退款时,跳转到申清退款页面等等的
const pages = getCurrentPages(); // 可以获取当前页面栈,上一个页面以及当前页面栈信息
console.log(pages); // 是一个数组,记录了上一个页面与当前页面信息
// 取到上一个页面
const prevPage = pages[pages.length - ]; // 获取第0个页面,也就是上个页面
console.log(prevPage);
prevPage.onLoad(); // 可以调用上一页面的方法
prevPage.setData({
name: 'itclanCoder',
});
当你通过wx.navigateTo()
,一层一层跳转到子页面时,使用getCurrentPages
方法就可以找到上级,上上级的页面栈信息
它是通过获取到其他页面的原型对象,然后通过小程序原型下的setData
方法,对当前对象管理的数据data
进行修改。这个方法getCurrentPage
方法可以操作页面堆栈页面的数据和方法,可以做到对子(后一)页面对父(上一)页面的数据管理
提示
getCurrentPages()
用于获取当前页面栈,数组中第一个元素为首页,最后一个元素为当前页面
- 不要尝试修改页面栈,会导致路由以及页面状态错误(不要依赖这个方法)
- 不要在
App.onLaunch
的时候调用getCurrentPages()
,此时page
还没有生成
3
使用全局变量
全局变量实际上是定义了一个全局的对象,并在每个页面中引入。在初始化代码的时候,小程序会读取一个 app.js 的文件,在这里我们可以定义我们所需要的全局变量。
全局页面 app.js
//app.js
...
App({
globalData : {
foo : 'bar'
}
});
然后在页面中,可以通过 getApp() 方法获取到全局应用对象,可以对全局变量进行读取并更改:
//page.js
...
var app = getApp()
var getFoo = app.globalData.foo
app.globalData.foo = 'fun'
由于 app.js 在项目中是用来做基础配置的,因此不建议将很多变量放在这里配置。一般情况下会将一些持久化的常量配置在这里,对于经常需要变动的量不建议用这个方法。
<view>
<view class="globalData">
<view class="getGloablBtn btn" bindtap="onGetGlobal">获取全局变量</view>
<view class="changeGloablBtn btn" bindtap="onChangeGlobal"
>修改全局变量</view
>
</view>
<view>{{token}}</view>
<view>{{url}}</view>
<view>{{userInfo}}</view>
</view>
分析
全局定义的变量,一些状态,可以挂载在全局页面 app.js
的globalData
中,在使用全局变量页面处
- 需要调用
getApp()
函数 - 通过
getApp().globalData.a
可以拿到全局对象下定义的变量对象 - 若要修改全局变量对象直接赋值即可
getApp().globalData.a = "bb"
;
全局定义变量注意事项
App()
必须在app.js
中注册,且不能注册多个- 不要在定义
App()
内的函数调用getApp()
,使用this
就可以拿到App
下的实例 - 不要在
App.onLaunch
的时候调用getCurrentPages()
,此时page
还没有生成 - 通过
getApp()
获取到全局页面的实例后,就不要私自调用生命周期函数了的
4
使用本地缓存
本地缓存是微信小程序提供的一个功能, 可以将用户产生的数据做本地的持久化,类似于 NoSQL,可以进行读取和修改的操作。那么在不同的页面之间,如何利用它,进行数据的交互呢?
假设我们在 A 页面保存了用户的信息。
// pageA.js
...
var developer = {
name: 'raymond',
gender: 'male'
}
wx.setStorageSync('developer', developer);
这样做,这个数据就存在了本地。当在B页面需要使用的时候,可以直接的获取到数据池中的数据,并进行 CRUD 操作:
//pageB.js
...
// Retrieve
var developer = (wx.getStorageSync('developer') || [])
// Update
developer.name = 'Jiayang'
wx.setStorageSync('developer', developer);
// Delete
wx.removeStorage({
key: 'developer'
})
需要注意的是,在回到 A 页面的时候,小程序需要重新读取数据。这时候,可以选择放在生命周期的 onShow 中对数据重新加载。在使用本地缓存,可以作为页面间数据传递,但是仍然需要注意一些实用情况,如下所示
隔离策略
同一个微信用户,同一个小程序 storage
上限为 10MB
,一般可以作为缓存临时一些小的数据,比如用户登录信息之类的
storage
以用户维度隔离,同一台设备上,A 用户无法读取到 B 用户的数据;不同小程序之间也无法互相读写数据
存储大小限制
除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。单个 key
允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB
插件隔离限制
- 同一小程序使用不同插件:不同插件之间,插件与小程序之间
storage
不互通。 - 不同小程序使用同一插件:同一插件
storage
不互通 storage
只是针对当前用户,不同用户,使用不同的插件,他们之间storage
是无法实现数据共用的
设置/获取/删除存储
使用的是wx.setStorageSync()
,wx.getStorageSync
方法
如何设置本地存储数据
wx.setStorage({
key: 'key',
data: 'value',
});
如何获取本地存储数据
wx.getStorageSync({
key: 'key',
success(res) {
console.log(res.data);
},
});
如何删除本地存储数据
清除小程序当中的本地存储分为:1、一次性全部删除所有存储,2、删除存储中某指定的存储key
wx.clearStorage(); // 一次性删除小程序中的所有存储数据
删除存储中某指定的存储key
,一定要注意这两者的区别,有的小伙伴只知道wx.clearStorage()
wx.removeStorageSync('key'); // 删除小程序中指定的key的存储
注意:wx.removeStorageSync
方法,不同于wx.clearStorageSync()
方法,它同样也是删除小程序中所有同步存储的数据, 前者需要指定删除存储对应的key
值,而后者不需要指定key
,它是一次性删除所有同步存储的代码
wx.clearStorageSync(); // 一次性删除小程序中所有同步存储的数据
解决相同 key 覆盖问题
在小程序中,当出现同名key
,后者key
覆盖前者是一个让人头疼的问题
具体解决
可以将需要存储数据存到一个数组当中,当需要使用时,取最后一个即可。至于若有增删操作,每次删除完某一数据后,重新在设置一次本地存储即可
let lists = wx.getStorageSync('lists'); // 先获取lists本地存储的数据
if (!lists) {
// 第一次判断缓存中有没有lists数据
lists = []; // 若没有,则存储设置一个空数组
}
lists.push(data); // 这里的data是要存储到本地中的数据
wx.setStorageSync('lists', lists); // 设置本地存储key:val
是使用同步存储还是异步存储
带有Sync
,这个表示的同步的操作,与之相对的不带后缀就是异步”。同步与异步是指的消息通讯机制。就是信息传来传去的时候是同步还异步。重点强调的是通讯这个动作。
我们往往先要判断一下缓存中是否有我们想要的那一数据的,否则若没有,在代码中使用了,就会报错。JavaScript
是单线程的,但是浏览器是多线程的.它的异步是借助事件实现的.具体可自行查看多线程与单线程相关知识的
5
父级往子级页面(模板)的数据传递
我们通常会在页面之间进行跳转、重定向的操作。这时候,我们可以选择将部分数据放在 url 里面,并在新页面 onLoad 的时候进行初始化。
pageC.js
...
// Navigate
wx.navigateTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
// Redirect
wx.redirectTo({
url: '../pageD/pageD?name=raymond&gender=male',
})
在 D 页面中,我们可以这样接收到到所传进来的参数:
// pageD.js
...
Page({
onLoad: function(option){
console.log(option.name + 'is' + option.gender)
this.setData({
option: option
})
}
})
wx.navigateTo 和 wx.redirectTo 不允许跳转到 tab 所包含的页面,只能用 wx.switchTab 跳转。需要注意的是,wx.switchTab 中的 url 不能传参数。 微信新提供的 wx.reLaunch 接口可以传入参数。 另外,在页面中我们通常会用到一些组件模板,因此在父子之间也会有相应的数据传递。使用 name 属性,作为模板的名字。然后在这里面使用 is 属性,声明需要的使用的模板。
{{index}}: {{msg}}
Time: {{time}}
然后将模板所需要的 data 传入,如:
<template is="msgItem" data="{{...item}}"/>
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
传入模板的除了变量,还可以是事件方法对象。例如,模板中的点击事件,可以传递到使用模板的元素中。
说明
将某整个父页面的数据传递给跳转到的子页面,是一个比较常见的需求。比如在商品详情页面中,跳到到下单页面,需要将详情页面的一些数据传递给跳转的子页面,那么这个时候,用url
的方式传递数据就不时很合适,选用eventChannel
的方式就比价适合。
在wx.navigateTo
的成功success
回调中,通过emit
进行触发,emit
接收两个参数,第一个是监听事件的名称,第二个参数是需要向目标页面传递的数据
res.eventChannel.emit(`监听的事件名称parentPageEmit`, { data: '数据' });
在跳转的目标页面中,通过调用getOpenerEventChannel
方法,然后进行on
的绑定
const eventChannel = this.getOpenerEventChannel();
eventChannel.on('监听的事件名称parentPageEmit', (data) => {
console.log(data);
this.setData({
acceptParentData: data,
});
});
当前页面-->
目标页面是利用wx.navigateTo
中的 success
回调中使用 emit
触发,目标跳转页面用 on
监听实现将当前页面的数据传递给目标页面中,那当前页面又如何获取目标页面的数据呢。
结语
在微信小程序中有以上并且不局限于以上几种的方式进行页面间数据传递、交互,在实际应用中可以组合使用。比如说:
- 一些常量,可以交由 app.js 管理;需要持久化的量可以放在本地保存。
- 涉及到下级页面或者模板元素的数据,可以通过传入参数的方式传入。
- 后级页面可以通过获取堆栈里的页面对象快速修改上级的数据。
在实际应用中结合使用,可以更好地管理小程序的数据。