背景
从 2000 年开始, xml
作为数据交换格式开始流行,服务器拼接 xml
接口,客户端 js 获取 xml
内容,动态修改页面。
几年后,数据量更小的 json
替代了 xml
。
移动互联网到来后,因为客户端分裂,加剧了接口的泛滥。
一转眼,接口已经玩了 20 年了。其他技术飞速发展,而前后端交互却一直是接口,没有什么创新。
js 已经有了 import
、 export
,为什么调用后端接口,不能像调用一个前端模块一样呢?
serverless,让这一切开始了变化。
亚马逊 lambda
提出了云函数的概念,不再使用 restful
的 url
,但仍然是基于 json
交换前后端数据。
uniCloud
最初也以支持云函数为开始,但我们发现这仍不够优雅、简洁、直观、统一。
从 HBuilderX 3.4
开始, uniCloud
推出了“ 云对象 ”,让调用云端服务,真正变成像调用前端模块一样简单。
什么是云对象
云对象:服务器编写 API,客户端调用 API,不再开发传输 json 的接口。思路更清晰、代码更精简。
比如服务端编写一个云对象 news,该对象导出若干方法:add()、getList()、getDetailById()、softDel()、changeContent()、allowPublic()、addComment()、getComments()...等方法。
客户端的 js 则可以直接 import
这个 news
云对象,然后直接调用 add
等方法。
服务器示例代码如下:
HBuilderX 中在 uniCloud/cloudfunctions 目录新建云函数/云对象,选择类型为云对象,起名为 news。打开云对象入口 index.obj.js
,添加一个 add 方法。
// 云对象名:news
module.exports = {
add(title, content) {
title = title.trim()
content = content.trim()
if(!title || !content) {
return {
errCode: 'INVALID_NEWS',
errMsg: '标题或内容不可为空'
}
}
// ...其他逻辑
return {
errCode: 0,
errMsg: '创建成功'
}
}
}
复制代码
然后在客户端的 js 中, import
这个 news
对象,调用它的 add
方法。
const news = uniCloud.importObject('news') //第一步导入云对象
async function add () {
try {
const res = await news.add('title demo', 'content demo') //导入云对象后就可以直接调用该对象的方法了,注意使用异步await
uni.showToast({
title: '创建成功'
})
} catch (e) { // 符合uniCloud响应体规范 https://uniapp.dcloud.net.cn/uniCloud/cf-functions?id=resformat,自动抛出此错误
}
}
复制代码
可以看到云对象的代码非常清晰,代码行数也只有 27 行。
而同样的逻辑,使用传统的接口方式则需要更多代码,见下:
// 传统方式调用云函数-云函数代码
// 云函数名:news
// 云函数入口index.js内容如下
'use strict';
exports.main = async (event, context) => {
const {
method,
params
} = event
switch(method) {
case 'add': {
let {
title,
content
} = params
title = title.trim()
content = content.trim()
if(!title || !content) {
return {
errCode: 'INVALID_NEWS',
errMsg: 'NEWS标题或内容不可为空'
}
}
// ...省略其他逻辑
return {
errCode: 0,
errMsg: '创建成功'
}
}
}
return {
errCode: 'METHOD_NOT_FOUND',
errMsg: `Method[${method}] not found`
}
};
复制代码
传统方式调用云函数-客户端代码
async function add () {
try {
const res = uniCloud.callFunction({
name: 'news',
data: {
method: 'add',
params: {
title: 'title demo',
content: 'content demo'
}
}
})
const {
errCode,
errMsg
} = res.result
if(errCode) {
uni.showModal({
title: '创建失败',
content: errMsg,
showCancel: false
})
return
}
uni.showToast({
title: '创建成功'
})
} catch (e) {
uni.showModal({
title: '创建失败',
content: e.message,
showCancel: false
})
}
}
复制代码
以上传统开发需要 68 行代码,对比云对象的 27 行代码,可以说“又丑又长”
更多强大功能
1. 预处理与后处理
无论请求云对象的哪个方法,开始前都会经过 _before
方法,结束会执行 _after
方法。这有助于统一的提前校验和格式化错误。
示例:
// news/index.obj.js
module.exports = {
_before: function(){
this.startTime = Date.now() // 在before内记录开始时间并在this上挂载,以供后续流程使用
const methodName = this.getMethodName() // 获取当前请求的云对象方法名
if(methodName === 'add' && !this.getUniIdToken()) {
throw new Error('token不存在')
}
},
add: function(title = '', content = '') {
if(title === 'abc') {
throw new Error('abc不是一个合法的news标题')
}
return {
errCode: 0,
errMsg: '创建成功'
}
},
_after(error, result) {
if(error) {
throw error // 如果方法抛出错误,也直接抛出不处理
}
result.timeCost = Date.now() - this.startTime
return result
}
}
复制代码
2. 云对象的返回值兼容 uniCloud 响应体规范
云对象返回值默认为 uniCloud响应体规范
,方便客户端统一拦截错误。
无论网络异常,还是 token 过期,都可以统一拦截提示。
详见 uniCloud响应体规范
3. 自动显示交互界面
每次写客户端联网的代码时,开发者都免不了重复写一堆代码:先调用 loading 等待框,联网结束后再关闭 loading,如果服务器异常则弹出提示框。
原来的写法(22 行):
uni.showLoading({
title: '联网中...'
})
uni.request({
url: "xxxx",
success: (res) => {
uni.showToast({
title: '添加成功',
icon: 'success',
mask: true,
duration: duration
});
},
fail: (err) => {
uni.showModal({
content: err.errMsg,
showCancel: false
});
},
complete: () => {
uni.hideLoading();
}
});
复制代码
现在,调用云对象的方法时,默认自带上述功能。
const news = uniCloud.importObject('news') //第一步导入云对象
try {
await news.add('title demo', 'content demo') //导入云对象后就可以直接调用该对象的方法了,注意使用异步await
uni.showToast({
title: '添加成功'
})
} catch (e) {}
复制代码
如上,原来需要 23 行的代码,现在 7 行就可以搞定!
当然,这些 UI 策略都可以自定义。
URL 化
为了历史兼容考虑,云对象同时提供了 URL 化方案。开发者仍然可以把一个云对象转换为一个 http
的 url
接口。
总结
使用云对象带来的诸多好处:
更清晰的逻辑
更精简的代码
更少的协作成本(以及矛盾~)
客户端调用时在 ide 里有完善的代码提示,方法参数均可提示。(传输 json
可没法在 ide
里提示)
自动支持 uniCloud 响应体规范,方便错误拦截和统一处理
更多云对象介绍,参见: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj.html
评论