页面导航、网络请求、数据缓存
页面导航
说到页面导航,我们可能首先想到的是页面跳转(页面 A 跳转到页面 B),页面跳转在不同端之间有不同的区别:
- H5 通过 window.history 属性对其进行访问,改变路由记录从而实现跳转
- iOS/安卓 是改变根视图或操作导航控制器出栈进栈从而实现跳转
- 小程序实现跳转采用的方式也是改变根视图或操作导航控制器出栈进栈
如果你要把用 Uniapp 开发的项目编译成 H5,那么该项目呈现的是单页面应用,单页面应用实现页面跳转是通过监测页面 url 的 hash 改变而加载不同页面。hash 模式背后的原理是 onhashchange 事件,可以在 window 对象上监听这个事件:
window.location.hash = "list/list"; // 设置页面 url 的 hash,会在当前url后加上 '#list/list'
let hash = window.location.hash; // '#/pages/list/list'
window.addEventListener("hashchange", function () {
// 监听 hash 变化,点击浏览器的前进后退或者hash改变会触发
});
例如访问列表地址,# 后面的路径就是指向页面地址:
http://localhost:8080/#/pages/list/list
如果不想要很丑的 hash,我们可以用路由的 history 模式,在项目的配置文件 【manifest.json】>>【h5 配置】>> 【路由模式】 进行修改:
history 模式改变 url 的方式会导致浏览器向服务器发送请求,如果服务器端未做任何处理,则会请求资源失败,我们需要在服务器端做处理:如果匹配不到任何静态资源,则应该始终返回同一个 html 页面。具体操作可以看 这里
如果你要用 Uniapp 开发的项目编译成微信小程序,就要注意微信小程序的页面栈的限制了,小程序中页面栈限制最多十层(微信进行了限制调整),随着页面栈的 push 增加,在不知道的情况下就会堆栈到十层,再用 API navigateTo 去跳转页面就跳不动了,用户会跳转失效(卡死状态)。
如果遇到上述问题,删除当前页面栈(redirectTo)或删除所有页面栈(reLaunch)来跳转了,页面栈以跳转的 url 为第一个页面栈。页面栈可以通过 getCurrentPages 方法获取。
function navigateTo(url, callback) {
let goType = getCurrentPages().length >= 10 ? "redirectTo" : "navigateTo";
wx[goType]({
url,
success: (res) => {
callback();
},
fail: (res) => {},
complete: (res) => {},
});
}
简单化是微信小程序的开发理念的其中之一,如果你的页面栈层出现爆栈卡制,那么可以考虑一下你的产品项目的入口是不是设计的太深了。
通过上面这些介绍,我们来看下 Uniapp 的路由与页面跳转
Uniapp 的路由与页面跳转
Uniapp 集成多端的跳转方式,以标签 navigator 及封装 API 的形式控制应用内的跳转。
如果想要首页跳转到列表页面并传一些参数:
// 在起始页面跳转到list.vue页面并传递参数
uni.navigateTo({
url: "/pages/list/list?id=1&name=uniapp",
});
// 或者使用标签形式跳转
<navigator url="/pages/list/list?id=1&name=uniapp">去列表</navigator>;
// 在list.vue页面接受参数
export default {
onLoad: function (option) {
//option为object类型,会序列化上个页面传递的参数
console.log(option.id); //打印出上个页面传递的参数
console.log(option.name); //打印出上个页面传递的参数
},
};
我们还可以使用下面的几个 API 操作页面跳转:
uni.navigateTo() 保留当前页面,跳转到应用内的某个页面,使用 uni.navigateBack 可以返回到原页面。
uni.redirectTo() 关闭当前页面,跳转到应用内的某个页面。
uni.reLaunch() 关闭所有页面,打开到应用内的某个页面。reLaunch 可以打开任意页面。
uni.switchTab() 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。switchTab 只能打开 tabBar 页面。
注意:
* navigateTo, redirectTo 只能打开非 tabBar 页面。
* 页面跳转路径有层级限制,不能无限制跳转新页面
* 跳转到 tabBar 页面只能使用 switchTab 跳转
* 路由 API 的目标页面必须是在 pages.json 里注册的 vue 页面。如果想打开 web url,在 App 平台可以使用 plus.runtime.openURL 或 web-view 组件;H5 平台使用 window.open;小程序平台使用 web-view 组件(url需在小程序的联网白名单中)。在 hello uni-app 中有个组件 ulink.vue 已对多端进行封装,可参考。
如果使用标签形式进行跳转改变标签 open-type
属性即可:
<navigator url="navigate/navigate?title=navigate" open-type="navigate">
跳转到新页面
</navigator>
open-type 跳转方式参数对应:
值 | 说明 | 平台差异说明 |
---|---|---|
navigate | 对应 uni.navigateTo 的功能 | |
redirect | 对应 uni.redirectTo 的功能 | |
switchTab | 对应 uni.switchTab 的功能 | |
reLaunch | 对应 uni.reLaunch 的功能 | 头条小程序不支持 |
navigateBack | 对应 uni.navigateBack 的功能 |
Uniapp 中的网络请求
Uniapp 使用 API uni.request()
发起网络请求,如果你用过微信小程序开发就会熟悉这个 API(wx.request()),Uniapp 兼容了微信代码,如果你写了 wx.
前缀,也可以执行,效果等同于 uni.
。
代码示例:
uni.request({
url: "https://www.example.com/request", //仅为示例,并非真实接口地址
data: {
text: "uni.request",
},
header: {
"custom-header": "hello", //自定义请求头信息
},
success: (res) => {
console.log(res.data);
this.text = "request success";
},
});
指定接口地址、请求方法、请求参数,可以拿来即用。如果没有传入 success / fail / complete 参数,则会返回封装后的 Promise 对象:
// Promise
uni
.request({
url: "https://www.example.com/request",
})
.then((data) => {
// data为一个数组,数组第一项为错误信息,第二项为返回数据
let [error, res] = data;
console.log(res.data);
});
如何中断一次请求呢?
很多场景下是如果请求 2 个接口数据,当某一个接口成功的时候,就可能需要禁止掉另外一个接口的继续请求了:
let requestTask = uni.request({
url: "https://www.example.com/request", // 仅为示例,并非真实接口地址。
});
// 中断请求任务
requestTask.abort();
上面对 request 对象进行一次返回,这样我们可以调用该对象下的 abort
方法,可中断请求任务。如果需要更好的实现拦截,并统一管理请求,Uniapp 插件市场有 flyio、axios 等三方封装的拦截器可用。
当然更多的时候,我们会进行另外一个操作,设置网络请求超时。
服务器未必会如同我们想的那么快捷,携带的信息也并非轻量,我们并不希望让这个请求一直保持触发状态。对于用户来说,这就是卡死的状态。Uniapp 规定可以统一在 manifest.json 文件中配置 networkTimeout 的参数:
"name" : "mvvm",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"networkTimeout" : {
"request" : 6000
},
// ...
有效封装能够更好的管理业务,比如服务器 500 错误的处理,400 的错误怎么去处理,这些就是让用户能在错误请求中获得良好体验。
一段 request 封装处理的响应代码:
const resInterceptor = (response, conf = {}) => {
// TODO do your response
const statusCode = response.statusCode;
console.log("statusCode:" + statusCode);
// response interceptor
if (statusCode >= 200 && statusCode < 300) {
//成功
_responseLog(response, conf, "response 200-299");
return response;
} else if (statusCode === 500) {
_responseLog(response, conf, "response 500");
// 为了对reject的内容更加可控,我们增加了一个控制字段 wakaryReqToReject
return {
// 根据当前字段来判断是否reject
wakaryReqToReject: true,
// 下面可以配置您的其它返回信息,方便您更加统一的处理reject的内容。
// 以下内容会被作为reject的返回,根据您的需要处理,比如返回您的具体错误信息
msg: "服务器错误",
res: response,
};
} else {
_responseLog(response, conf, "response 300-499");
// 为了对reject的内容更加可控,我们增加了一个控制字段 wakaryReqToReject
return {
// 根据当前字段来判断是否reject
wakaryReqToReject: true,
// 下面可以配置您的其它返回信息,方便您更加统一的处理reject的内容。
// 以下内容会被作为reject的返回,根据您的需要处理,比如返回您的具体错误信息
msg: "这里是提示信息",
res: response,
};
}
};
注意:
- 良好体验的 APP,还会判断当前是否处于飞行模式、是 wifi 还是 蜂窝数据
- 单次网络请求数据量建议控制在 50K 以下(仅指 JSON 数据,不含图片),过多数据应分页获取,以提升应用体验。
- localhost、127.0.0.1 等服务器地址,只能在电脑端运行,手机端连接时不能访问,请使用标准 IP 并保证手机能连接电脑网络
Uniapp 中的 storage 存储信息
Uniapp 集成了小程序,app,h5 的数据缓存,统一了 uni.setStorage()
,uni.setStorage()
系列 API,完成对缓存数据的操作。
示例代码:
uni.setStorage({
key: "storage_key",
data: "hello",
success: function () {
console.log("success");
},
});
将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象。
记住,uniapp 设置存储有同步与异步之分,使用 uni.setStorage()
参数对象需指定键值 key
,存储数据 data
:
// 异步需指定 key 和 data
uni.setStorage({
key: "storage_key",
data: "hello",
success: function () {
console.log("success");
},
});
// 同步,不用指定 key 和 data 的键
uni.setStorageSync("storage_key", "hello");
因为是异步操作,有接口调用成功的回调函数,如果有业务逻辑处理或者判定失败等情况,就可以从这入手。
注意:
Uniapp 的 Storage 在不同端的实现不同:
- H5 端为 localStorage,浏览器限制 5M 大小,持久化,可能会被清理
- App 端为原生的 plus.storage,无大小限制,不是缓存,持久化
- 各个小程序端为其自带的 storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
- 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
小结
- 页面跳转的方式是一个应用的基本,但是入口太深会让你的用户失去耐心;
- 请求的二次封装可以更好的管理你的请求动作,取消中断操作,业务问题,服务器错误等;
- 留意操作 Storage 的异步同步之分,以及 Storage 在各端的表现;