在前端页面开发中,大部分的时间都是在与后端进行数据交互:获取数据、计算并渲染。而页面上又有大量的元素状态需要维护,显示、隐藏、变化。这些都可能让我们焦头烂额,然后在一周后看不懂自己的代码。
所以项目开发的过程中需要一个规范来约束代码的走向,让代码能按照统一的、最高效的方式运行(还有让别人阅读)。这里介绍一个前端对接后端接口数据的一个最佳实践。
先让我们看看反例,不知道你是不是用过这样的 app:
- 点了一个按钮没有反应(???这按钮坏了),但是突然页面像爆炸了一样不停的刷新(-,-啊救命)
- 进入一个页面,是个纯白的(???网卡了?程序报错了?),返回再进还是纯白,让你搞不清楚到底发生了什么。
这里的例子说明:如果前端开发中不能把异常描述清楚、涵盖全面,数据状态的糟糕反馈就会直接影响用户体验。
问题分析
我们先从最简单的情况入手,一个页面使用一个接口。这种情况下通常是:
- 全量获取列表
- 获取主页详情
- 发布一张图片
- 搜索关键词
- ···
这样的情况又分两种,
- 进入页面时获取数据 -> 渲染页面
- 进入页面后进行操作 -> 得到反馈 -> 渲染页面。
不论是哪一种情况,我们都只在一个页面里处理一个接口,这是最简单的情况。那么我们来看一下下面的图片,并把它当作一个开发任务思考一下你会怎么处理。
如果你只想到了「调用接口」「渲染页面」里,那你这篇文章就是为你写的(笑)。其实上面的图只向你展示了两个状态:「初始状态」、「理想结果状态」,我用了「理想结果」这个词来描述这个状态,是因为这是我们在一切操作都完美的情况下得到的理想状态。
而通常在项目里你只会从别人手里得到这两张图,我说的对吗?(产品经理和设计师都默认你了解他们需要的一切)。
如果我们希望做一个优秀的前端,我们就需要立刻发现这里还缺少了三张图(三个状态)(有些交互里并不需要这么多状态,这里只讨论最全面的情况)
数据获取中状态
无数据状态
数据异常状态
需求分析
从调用一个接口到渲染页面我们大致分为一下几部
调用接口 -> 得到数据 -> 处理数据 -> 渲染
初始状态
接下来我们来编写一些代码,来对接接口并且管理数据和状态。为了使代码更加聚合,用一个字面量对象 SeaerchInput
来维护状态。然后我们模拟一个接口的调用。
// 以上面搜索为例
// 创建页面对象
let SearchInput = {}
// 模拟一个接口
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = [{
name: '李三'
}];
resolve(result);
})
})
}
理想结果状态
接下来我们在SearchInput
中用data
字段保存数据,用getSearchResult()
方法绑定数据,调用接口并直接绑定数据,那么我们将得到的「理想结果状态」。
let SearchInput = {
data: null,
getSearchResult() {
API.then(
(res) => {
this.data = res; // 绑定数据
}
)
}
}
SearchInput.getSearchResult(); // 获取数据
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
// html 的语法将使用 angular 指令去表达
这样的代码是十分脆弱的,因为我们已经默认数据会瞬间返回并且没有任何问题。
数据获取中状态
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = [{
name: '李三'
}];
resolve(result);
}, 3000) // 为接口增加3秒的延时
})
}
一旦给API
增加点延时,就会发现页面会在纯白状态下停留很久,因为页面没有任何提示,所以用户根本无法知道发生了什么事情,是等待还是返回?
为此我们需要管理从接口发起请求(request)到接收响应(response)这段时间的状态,在SearchInput
中用hasDone
来保存接口的响应状态,null
代表这个接口还在初始化状态,false
代表已经发出请求但未收到响应,true
代表已经收到响应。
let SearchInput = {
data: null,
hasDone: null, // 初始化
getSearchResult() {
this.hasDone = false; // 发起请求时置为 false
API.then(
(res) => {
this.hasDone = true; // 收到响应时置为 true
this.data = res;
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
// ...
}, 3000) // 为接口增加3秒的延时
})
}
loading
这下好了,如果接口很慢页面也会显示 loading,用户不会为此不知所措了。
数据异常状态
尽管现在网络和服务器已经十分稳定,很少会出现异常,但是无论是网络、服务器或代码哪一个出现异常而没有考虑,那都会造成用不好的用户体验
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let error = '服务器异常';
reject(error); // 接口返回了异常
})
})
}
现在我们假设我们的API
返回了异常,页面又会变为纯白了,没有任何数据显示也没有任何提示。
为此我们需要一个状态来管理接口返回的状态,在SearchInput
中用hasSuccess
来保存接口的返回状态,null
代表还在初始化状态,false
代表接口返回失败,true
代表接口成功返回数据。(你甚至可以先判断数据的格式、数量等是否满足你的要求,如果不满足要求,即使接口返回了数据,你一样可以将hasSuccess
设置为false
,因为这里的 success 代表了你得到了可以正确使用的数据,而不仅仅是得到了数据)
let SearchInput = {
data: null,
hasDone: null,
hasSuccess: null, // 初始化
getSearchResult() {
this.hasDone = false;
API.then(
(res) => {
this.hasDone = true;
this.hasSuccess = true; // 得到数据置为 true
this.data = res;
},
(err) => {
this.hasDone = true; // 此时我们也要更新 hasDone
this.hasSuccess = false; // 发生异常置为 false
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
loading
数据异常
现在我们会在hasDone === true
后知道数据是否正常,并且给出了错误的提示。
无数据状态
最后一个状态也是我们要考虑的,当用户尝试搜索一个词却什么都没返回,又变成了可恶的纯白界面,我们还需要考虑一下当获取数据时什么都没有的情况。
function API () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
let result = []; // 现在没有任何结果
resolve(result);
})
})
}
我们需要一个状态来管理数据的状态,在SearchInput
中用hasData
来保存数据状态,null
代表还在初始化中,false
代表数据为空,true
代表数据不为空。
let SearchInput = {
data: null,
hasDone: null,
hasSuccess: null,
hasData: null, // 初始化
getSearchResult() {
this.hasDone = false;
API.then(
(res) => {
this.hasDone = true;
this.hasSuccess = true;
this.hasData = res.length > 0; // 有置为 true,没有数据置为 false
this.data = res;
},
(err) => {
this.hasDone = true;
this.hasSuccess = false;
this.hasData = false; // 失败肯定没有数据了
}
)
}
}
SearchInput.getSearchResult();
function API () {
return new Promise(function (resolve, reject) {
// ...
})
}
loading
数据异常
数据异常
现在上面的代码基本上就是你所需要的了,它可以帮你应对各种情况,让页面展示的更加完美。
实践分析
这一大段代码就是对应一个简单接口五个状态的设计,也是我目前项目中使用的模式,虽然看上去比较繁琐,但是相比后期再不停的补充和修改,一次性考虑全面带来很多好处。
如果一个接口是为了实现分页加载,那么状态的数量又会有所提升,这篇文章不再阐述。
如果一个页面使用了多个接口,数据和状态之间产生了交叉,为了使状态逻辑清晰应该合理利用字面量对象来聚合代码逻辑。
在多人协作方面,由于大家使用同一套规范,对代码的阅读速度有显著提高。
这里列出的代码以普及为主,很多实现细节方面都可以再去优化,提炼。甚至写一个构造函数也是很方便的选择。
罗小黑写写文字
如果喜欢文章 请留下一个赞~
如果喜欢文章 分享给更多人~在掘金中关注我
在中关注我自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
转载时请保留原文链接 以保证可及时获取对文章的订正和修改