Appearance
createIO
集成知晓云表操作(读/写数据表)、云函数调用、后端接口调用,并且引入了 @tanstack/vue-query(下文简称 vue-query),能够满足更多场景的使用
功能
- 将表操作、云函数调用、接口调用统一封装到 io 内,这样做的好处有
- 抛弃 BaaS.invoke('cloudFunctionName') 形式调用云函数,改为 io.faas.cloudFunctionName,减少魔法值的使用
- 可以将云函数、api 归并到一起,避免在不同的地方进行对云函数、api 重复声明
- 增加 typescript declaration,可以更加便捷高效地调用 io
- 对 vue-query 简易封装
- 新增 IOError,所有的 io operation 执行错误时都会抛出统一的错误对象,方便错误收集上报以及错误信息提示
使用方法
createIO
js
import {createIO} from '@ifanrx/uni-mp'
const io = createIO({
// 知晓云数据表配置
table: {
/**
* key 将作为该表在 io 中的引用名字,value 为该表在知晓云内表名
* key 必须为 camelCase
*/
userprofile: '_userprofile',
settings: 'settings',
likesLog: 'likes_log',
},
/**
* 后端接口配置
* api 若不配置 method,默认会使用 GET 发送请求
* 通过 io.api.apiKey 调用
*/
api: {
getUserprofile: {
method: 'POST',
url: '/xxx',
},
},
/**
* 云函数配置
* 通过 io.faas.faasKey 调用
* key 必须为 camelCase
*/
faas: {
helloWorld: 'hello_world',
},
})
export default io
基础功能
query
构建知晓云 table 查询条件
js
// 更多可参考:https://doc.minapp.com/js-sdk/schema/frag/query.html
const query = io.query.compare('key', '=', 'something')
user
构建知晓云用户实例
js
// 更多可参考:https://doc.minapp.com/js-sdk/user.html
const user = io.user
user.get(userID)
file
构建知晓云文件实例
js
// 更多可参考:https://doc.minapp.com/js-sdk/file/
const file = io.file
file.upload()
uploadFile
上传单个文件到知晓云
支持大文件断点续传,通过 options.isMultipart 控制,查看更多
js
const uploadTask = io.uploadFile(file, options)
uploadTask.onProgressUpdate(e => {
// https://doc.minapp.com/js-sdk/file/file.html#监听上传进度变化事件和中断上传任务-仅限微信小程序
console.log(e)
})
uploadFiles
上传多个文件到知晓云
js
const uploadTask = io.uploadFiles(files, options)
getServerDate
获取服务器时间
js
const serverDate = await io.getServerDate()
console.log(serverDate) // dayjs 对象
useServerDate
获取响应式服务器时间,详见io.useServerDate
js
const serverDate = await io.useServerDate()
console.log(serverDate.value) // dayjs 对象
useQuery
基于 vue-quey 封装,详见io.useQuery
js
const {data: userprofile} = io.useQuery(() => io.userprofile.find())
useQueryAll
io.useQueryAll,专门用来处理并行请求,详见io.useQueryAll
js
const tasks = [() => io.userprofile.first(), () => io.settings.first()]
const {
data: [userprofile, settings],
} = io.useQueryAll(tasks)
useRequest
io.useRequest,详见io.useRequest
js
const {data: userprofile} = io.useRequest(
io.userprofile.find, // requestFn
{query: io.query.compare('id', '=', 'xxxxxx')}, // requestParams
{staleTime: 10 * 60 * 1000} // queryOptions
)
useMutation
io.useMutation, 没有任何封装,相当于调用 vue-query 的 useMutation
js
const {mutate, data} = io.useMutation({
mutationFn: () => {},
mutationKey: ['key'],
})
知晓云数据表读写操作
get
根据 record id 获取对应记录
js
io.userprofile.get({id: 'xxx'})
find
根据查找条件找到记录列表
js
io.userprofile.find({query: io.query, limit: 20, offset: 0})
first
根据查找条件找到第一条满足条件的记录
js
io.userprofile.first({query: io.query})
update
已知记录 id,修改数据记录
js
io.userprofile.update({id: 'xxx', data: {name: 'xxx'}})
updateMany
根据查找条件修改记录列表
js
io.userprofile.updateMany({query: io.query, data: {name: 'xxx'}})
delete
已知记录 id,删除记录
js
io.userprofile.delete({id: 'xxx'})
deleteMany
根据查找条件删除记录列表
js
io.userprofile.deleteMany({query: io.query})
create
创建单条记录
js
io.userprofile.create({name: 'xxx'})
createMany
创建多条记录
js
io.userprofile.createMany([{name: '张三'}, {name: '李四'}])
count
获取满足当前查找条件的记录数量
js
io.userprofile.count({query: io.query})
get/find/first/update/updateMany/create 都拥有 plain 参数,当 plain 为 true 时,会将响应数据格式化,返回 response.data(find 操作是个另外,当 plain 为 true 时会返回 response.data.objects),当 plain 为 false 时,则会将完整的 response 返回。plain 默认值都是 true
云函数调用
对知晓云 sdk 的 BaaS.invoke 进行封装,省略了调用 invoke 时对 functionName 参数的传入,让云函数的调用更加便捷,同时支持代码提示
当云函数响应的 code 为 0 时(此时云函数执行成功),返回云函数响应结构中的 data 字段,否则会抛出 IOError
类型定义
ts
faas: {
[P in keyof T]: <
TRequestPayload extends RequestPayload = RequestPayload,
TSync extends boolean = true
>(
payload?: TRequestPayload,
sync?: TSync
) => IOPromise<CloudFunctionResponse<TSync>>
}
- payload 为请求云函数参数,sync 表示是否以同步的形式调用云函数
- 若以同步的方式执行云函数,该方法会在云函数执行完成后返回
- 若以异步的方式执行云函数,云函数会立刻返回 {status: "ok"},且无法获取云函数的执行结果
- 同步与异步云函数有不同的超时时间,同步云函数为 5 秒,而异步云函数为 5 分钟
- 默认使用同步的形式调用云函数
- 更多内容请查阅文档 BaaS.invoke
example
js
const io = createIO({
faas: {
helloWorld: 'hello_world',
},
})
// 默认使用同步的形式调用云函数
io.faas.helloWorld({message: 'hello world'})
// 使用异步的形式调用云函数
io.faas.helloWorld({message: 'hello world'}, false).then(console.log) // {status: 'ok'}
后端接口调用
一般来说,在微信小程序内请求后端接口都需要依赖知晓云 sdk 的 BaaS.request,使用用户在知晓云登录获取的 token 来完成鉴权
后端接口请求支持与云函数一致的调用方式,同样支持代码提示
需要注意的是,在调用 createIO 配置 api 时,需要传入该请求对应的 request method,不传默认使用 GET 请求。并且只能用来处理内部的 api,内部 api 有统一的响应结构
ts
{
status: string // 取值有 ok | error
error_code: integer, // 错误状态码
error_msg: string // 底层的错误信息
display_error_msg: string // 前端显示的错误信息
data: {} // 正常数据
}
当 status 为 error 时,抛出 IOError,否则返回 data
类型定义
ts
declare type ApiMethod =
| 'GET'
| 'OPTIONS'
| 'HEAD'
| 'POST'
| 'PUT'
| 'DELETE'
| 'TRACE'
| 'CONNECT'
declare interface ApiConfig {
method?: ApiMethod
url: string
}
declare type Api = Record<string, ApiConfig>
declare interface ApiRequestOptions {
header?: object
dataType?: string
}
declare interface ApiOperation<T extends Api> {
api: {
[P in keyof T]: (
payload?: RequestPayload,
options?: ApiRequestOptions
) => IOPromise<ApiResponse>
}
}
example
js
const io = createIO({
api: {
getUserprofile: {
method: 'POST',
url: '/xxxx',
},
},
})
io.api.getUserprofile({message: 'hello world'}, {header: {xxx: 'xxx'}})
Hooks
io.useQuery
io.useQuery 对 vue-query 的 useQuery 进行了封装,简化了调用方式
主要调整了 queryFn 参数的传入方式,并用 uuid 设置了默认的 queryKey。如有需要也可以传入自定义的 queryKey
其他保持不变,queryOptions 可参考官网:useQuery
example
js
// 单个请求
const {isFetching, data: userprofile} = io.useQuery(() => io.userprofile.first(), optionsQuery)
// 依赖上一个请求的串行请求
const userId = computed(() => userprofile.value?.id)
const enabledRequestLog = computed(() => !!userId.value)
const {
isFetching,
data: log,
} = io.useQuery(() => {
return io.log.get(userId.value)
}, {
enabled: enabledRequestLog,
})
useWatchLoading(!isFetching, '加载中...')
// or
<template>
<div v-if="!isFetching">加载中...</div>
<div v-else>
page content
</div>
</template>
io.useQueryAll / io.useQueryAllSettled
基于 io.useQuery 进行二次封装,专门用来应对并行请求的情况
第一个参数必须为数组,且数组元素必须为返回 Promise 的函数
返回 data 可以直接解构,其他与 io.useQuery 一致
io.useQueryAll / io.useQueryAllSettled 的区别与 Promise.all 和 Promise.allSettled 一致
example
js
const tasks = [() => io.userprofile.first(), () => io.settings.first()]
const {
isFetching,
data: [userprofile, settings],
} = io.useQueryAll(tasks)
useWatchLoading(isFetching, '加载中...')
io.useRequest
如果有缓存请求的需要,可调用 io.useRequest
io.useRequest 是对 vue-query 的 useQuery 二次封装
参数与 useQuery 有很大不同,应特别留意
为了避免在使用时使用了重复/错误的 queryKey,io.useRequest 会根据 requestFn 的表名、查询方法和参数自动填充 queryKey
io.useRequest 的响应以及 queryOptions 的详情可参考官网:https://tanstack.com/query/v5/docs/vue/reference/useQuery
实现上在每个 io operation 下面都会有一个 queryKey,包括数据表读写请求、云函数、api,比如说:
js
console.log(io.userprofile.find.queryKey) // ['userprofile', 'find']
console.log(io.settings.count.queryKey) // ['settings', 'count']
console.log(io.faas.helloWorld.queryKey) // ['faas', 'helloWorld']
console.log(io.api.getUserprofile.queryKey) // ['api', 'getUserprofile']
通常情况下不需要主动去使用 queryKey,在调用 io.useRequest 时,会自动取该 operation 的 queryKey,拼接请求参数生成真正的 queryKey,如下:
js
// 函数定义
useRequest(request, requestParams, queryOptions = {}) {
if (isEmpty(request.queryKey)) {
throw new TypeError('request fn 只能使用 io 内的方法')
}
const queryKey = (queryOptions.queryKey || request.queryKey).concat(requestParams)
const queryResult = useQuery(
({queryKey}) => {
const [params] = queryKey.slice(-1)
return request(clone(params))
},
{
...queryOptions,
queryKey,
}
)
return Object.assign(queryResult, {queryKey})
}
TIP
useRequest 的 requestParams 支持使用响应式参数,参数发生变化时会重新请求
特别地,io.useRequest 只适用于 io 内的方法,其他场景下若需要用到 useQuery,需使用的 io.useQuery
example
js
// 基本用法
const {queryKey, data} = io.useRequest(io.faas.helloWorld, {
message: 'hello world',
})
console.log(queryKey) //['faas', 'helloWorld', {message: 'hello world'}]
io.useMutation
io.useMutation 会将 vue-query 的 useMutation 原封不动抛出,具体请参照官方文档: https://tanstack.com/query/v5/docs/vue/reference/useMutation
io.useServerDate
通过 io.useServerDate() 可以得到一个响应式的 Dayjs 对象,记为 serverDate。serverDate 会在收到知晓云接口响应时刷新,在没有请求发出前 serverDate 为本地时间
- 需要特别注意的是:serverDate 的状态是全局共享的
原理是给 BaaS.request 增加一个响应拦截器,每当接收到知晓云接口响应时,会重新将 response.header.Date 赋值给 serverDate
同时 io.useServerDate 可以接收一个 immediate 参数,表示立刻调用 BaaS.getServerDate 来获取最新的服务器时间并且更新 serverDate(但是不应该滥用 immediate,正常情况下都不需要传入 immediate 参数,每当请求发出时会自动根据响应头内的 Date 来更新缓存)
通常情况下,在视图层使用到服务器时间,更推荐使用 io.useServerDate,在逻辑层需要使用服务器时间做校验,更推荐使用 io.getServerDate,可根据实际应用场景来选择
example
js
// 使用服务器时间计算 relativeTime 并渲染到视图层
const serverDate = io.useServerDate()
// mock message
const message = {created_at: dayjs('2022-11-24 09:00').unix(), content: 'Welcome.'}
<template>
<view>
<view>{{ message.content }}</view>
<!-- 当有新的请求发出时,该 relativeTime 会刷新,除非当前页面已经不在页面栈内 -->
<view>{{ serverDate.to(dayjs.unix(message.created_at)) }}</view>
</view>
</template>
js
// 使用服务器时间做校验
// mock activity
const activity = {
starts_at: dayjs('2022-11-24 09:00').unix(),
ends_at: dayjs('2022-11-28 09:00').unix(),
}
async function submitForm() {
const serverDate = await io.getServerDate()
const startsAt = dayjs.unix(activity.starts_at)
const endsAt = dayjs.unix(activity.ends_at)
if (serverDate.isBefore(startsAt)) {
return '未开始'
}
if (serverDate.isAfter(endsAt)) {
return '已结束'
}
// ...something else
}
IOError
io 内所有方法执行出错时,都会抛出 IOError,方便统一错误处理以及错误上报
类型定义
js
class IOError extends Error {
code: number
displayMessage: string
info: Record<string, any>
}
TIP
通常情况下,可以配合 message.showIOErrorModal 使用,以便轻松地将错误信息显示给用户。
js
try {
// 调用 io 方法
} catch (error) {
message.showIOErrorModal(error, {
10001: '优先显示自定义报错',
'request timeout': '网络超时',
default: '当最终文案为空时,使用该默认回退文案',
})
}