Skip to content

createIO

集成知晓云表操作(读/写数据表)、云函数调用、后端接口调用,并且引入了 @tanstack/vue-query(下文简称 vue-query),能够满足更多场景的使用

功能

  • 将表操作、云函数调用、接口调用统一封装到 io 内,这样做的好处有
    • 抛弃 BaaS.invoke('cloudFunctionName') 形式调用云函数,改为 io.faas.cloudFunctionName,减少魔法值的使用
    • 可以将云函数、api 归并到一起,避免在不同的地方进行对云函数、api 重复声明
  • 增加 typescript declaration,可以更加便捷高效地调用 io
    • image
  • 对 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: '当最终文案为空时,使用该默认回退文案',
  })
}