npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cj-query-adapter-vue

v1.1.5

Published

A Vue adapter for query management, providing hooks for caching and synchronizing asynchronous and remote data

Downloads

99

Readme

cj-query-adapter-vue 使用文档

目录

  1. 简介
  2. 安装
  3. 基础概念
  4. 基本用法
  5. API 详解
  6. 配置选项
  7. 高级用法
  8. 缓存策略
  9. 微服务名称空间
  10. 查询操作
  11. 配置合并策略
  12. 测试

简介

cj-query-adapter-vue 是一个基于 cj-vue-query 的 Vue 适配器,提供统一的查询/分页/变更(Mutation)能力,并在类型层面为 Vue 2 与 Vue 3 进行适配。它支持:

  • 数据查询与缓存管理
  • 分页查询与无限滚动
  • 统一的请求数据映射(params/body/url)与响应转换
  • 查询状态管理、自动重试、错误处理
  • 响应式数据绑定
  • 便捷的 Query 操作(invalidate/refetch/remove/cancel/reset/getData/setData)

安装

npm install cj-query-adapter-vue

基础概念

核心组件

  • CreateQueryFactory: 创建查询工厂函数
  • QueryAdapter: 查询适配器接口,提供各种查询方法
  • useQuery: 普通查询 Hook
  • useInfiniteQuery: 分页查询 Hook
  • useMutation: 变更操作 Hook
  • useAction: 查询操作工具

类型参数

  • P (Payload): 请求参数类型
  • D (Data): 原始响应数据类型
  • T (Transformed): 转换后的响应数据类型

基本用法

创建查询

import { CreateQueryFactory } from 'cj-query-adapter-vue'

// 约定的 Service 入参/出参类型见下文 API
const service = async (config) => {
  const response = await fetch(config.url, {
    method: config.method,
    body: config.body ? JSON.stringify(config.body) : undefined,
    headers: { 'Content-Type': 'application/json' },
  })
  return response.json()
}

// 创建查询工厂
const defineQuery = CreateQueryFactory(service)

// 定义一个用户详情查询(演示 :id 占位符自动替换)
const getUserById = defineQuery<{ id: number }, User>({
  url: '/api/user/:id',
  method: 'GET',
})

// 定义一个用户列表查询
const getUserList = defineQuery<{ pageSize?: number; pageNo?: number }, PaginatedResponse<User>>({
  url: '/api/users',
  method: 'GET',
})

// 定义一个创建用户的变更
const createUser = defineQuery<Omit<User, 'id'>, User>({
  url: '/api/users',
  method: 'POST',
})

使用查询

<script setup>
import { getUserById } from './api'

// 使用查询钩子(传入函数以支持响应式依赖;返回 null 可禁用请求)
const { data, isLoading, isError, error } = getUserById.useQuery(() => ({ id: 1 }))
</script>

<template>
  <div>
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="isError">加载失败: {{ error?.message }}</div>
    <div v-else-if="data">
      <h2>{{ data.name }}</h2>
      <p>{{ data.email }}</p>
    </div>
  </div>
</template>

使用分页查询(无限加载)

<script setup>
import { getUserList } from './api'

const {
  data,
  isLoading,
  isError,
  error,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
  flatData,
} = getUserList.useInfiniteQuery(() => ({ pageSize: 10 }))
</script>

<template>
  <div>
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="isError">加载失败: {{ error?.message }}</div>
    <div v-else>
      <ul>
        <li v-for="user in flatData" :key="user.id">
          {{ user.name }} - {{ user.email }}
        </li>
      </ul>

      <button v-if="hasNextPage" @click="fetchNextPage" :disabled="isFetchingNextPage">
        {{ isFetchingNextPage ? '加载中...' : '加载更多' }}
      </button>
      <div v-else>没有更多数据了</div>
    </div>
  </div>
</template>

API 详解

CreateQueryFactory

创建一个查询工厂函数,用于定义查询与变更。

function CreateQueryFactory(
  service: Service,
  queryClient?: QueryClient,
  baseOptions?: BaseAdapterOptions<any, any>, // 基础配置,不包含 isCache
): <P = unknown, D = unknown, T = D>(
  serviceOptions: ServiceOptions<P, D, T>,
  defaultOptions?: AdapterOptions<T, D>, // 实例默认配置,包含 isCache
  serviceConfig?: FnDataType<P, T>,
) => QueryAdapter<P, D, T>
  • service: 实际执行网络请求的服务函数
  • queryClient?: 可选的查询客户端实例
  • baseOptions?: 适配器级别的基础配置(如全局的 staleTime、retry 等,注意此层级不包含 isCache
  • defaultOptions?: 当前查询/变更实例的默认配置
  • serviceConfig?: 额外的服务配置

Service / BaseServiceConfig

export interface BaseServiceConfig<P> extends OtherOptions {
  url: string
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
  body: P | undefined
  params: P | undefined
}
export type Service = <P, D>(options: BaseServiceConfig<P>) => Promise<D>

ServiceOptions

定义查询服务的配置选项,包含 URL 构建、参数处理等配置。

interface BaseUrlOptions {
  /**
   * 请求的 url,同时也是 query key 的一部分。
   * 常用: '/api/user/list',带参数: '/api/user/:id'
   * 请求 url 计算优先级:selectUrl > buildDataToUrl > options.url
   */
  url: string
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
  /** POST 等请求的 body 是否进行 FormData 转换 */
  formData?: boolean
  /** 是否使用数据对 url 中的占位符进行替换,默认 true */
  buildDataToUrl?: boolean
  /** 是否剔除被替换进 url 的字段,默认 true */
  cullBuildData?: boolean
}

export interface ServiceOptions<P = any, D = any, T = D> extends BaseUrlOptions, OtherOptions {
  /**
   * 自定义请求 url 的生成,不影响 query key。
   * 一般无需拼接查询字符串,库会处理 `params`。
   */
  selectUrl?: (data: P, options: BaseUrlOptions) => string
  /**
   * 定义 body 的生成:
   * - true:使用默认逻辑(非 GET 且有数据则作为 body)
   * - null:不传 body
   * - function:自定义生成体
   */
  selectBody?: true | null | ((data: P, options: BaseUrlOptions) => object)
  /**
   * 定义 params 的生成:
   * - true:直接映射(GET 默认)
   * - null:不传 params
   * - function:基于 data 生成对象
   */
  selectParams?: true | null | ((data: P, options: BaseUrlOptions) => object)
  /**
   * 转换响应数据
   */
  transformResponse?: (response: D, requestData?: P) => T
}

QueryAdapter

查询适配器接口,包含所有可用的查询方法。

interface QueryAdapter<P = any, D = any, T = D> {
  (data: P): Promise<T>
  /** 请求路径 + 方法的拼接字符串。例如:GET:/api/user/:id */
  _key: string
  /** 同 _key,兼容旧命名 */
  queryKey: string
  /** 为 QueryClient 提供模糊匹配功能的基础 key */
  baseQueryKey: [string]
  /**
   * 生成用于与 QueryClient 精确匹配的 queryKey。
   * - requestId 仅在 isCache=-1(不共享缓存)时作为 key 的一部分。
   */
  spliceQueryKey: (
    queryData?: MakeOptional<P> | null,
    requestId?: RequestIdType,
    isToValue?: boolean,
  ) => [string, undefined | RequestIdType, (MakeOptional<P> | null)?]

  /**
   * 生成 useQuery/useInfiniteQuery 所需的 options(内部使用)。
   */
  _createQueryOptions: <C extends QueryConfig<T> | QueryInfiniteConfig<T>>(
    queryData?: MakeOptional<P>,
    queryConfig?: C,
  ) => QueryOptionResult<C, T>

  /** 普通查询 */
  useQuery: ((
    queryData?: MakeOptional<P>,
    queryConfig?: QueryConfig<T> & { initialData?: undefined },
  ) => UseQueryReturnType<T, Error>) & ((
    queryData?: MakeOptional<P>,
    queryConfig?: QueryConfig<T> & { initialData: NonUndefinedGuard<T> | (() => NonUndefinedGuard<T>) },
  ) => UseQueryDefinedReturnType<T, Error>)

  /** 分页查询(无限加载) */
  useInfiniteQuery: (
    queryData?: MakeOptional<P>,
    queryConfig?: QueryInfiniteConfig<T>,
  ) => UseInfiniteQueryReturnType<InfiniteData<T>, Error> & { flatData: Ref<PaginatingRecordPage<T>> }

  /** 变更(提交数据) */
  useMutation: <V = P>(mutationConfig?: MutationOptions<T, P, V>) => UseMutationReturnType<T, Error, V, any>

  /** 操作查询(invalidate/refetch/remove/cancel/reset/getData/setData) */
  useAction: (baseQueryData?: MakeOptional<P>) => {
    invalidate: (queryData?: MakeOptional<P>, filters?: any, options?: any) => Promise<void>
    refetch: (queryData?: MakeOptional<P>, filters?: any, options?: any) => Promise<void>
    remove: (queryData?: MakeOptional<P>, filters?: any) => void
    cancel: (queryData?: MakeOptional<P>, filters?: any, options?: any) => Promise<void>
    reset: (queryData?: MakeOptional<P>, filters?: any, options?: any) => Promise<void>
    getData: (queryData?: MakeOptional<P>) => D | undefined
    setData: (data: D, queryData?: MakeOptional<P>) => void
  }
}

配置选项

QueryConfig

普通查询的配置选项:

interface QueryConfig<D> extends Omit<QueryOptions<D>, 'queryKey' | 'queryFn' | 'enabled'> {
  /** 额外:控制缓存策略(默认 0)
   * - -1: 禁用缓存。数据仅在组件生命周期内有效,不共享,组件卸载即清除;需要 `requireId` 以区分多个组件实例。
   * - 0: 启用缓存,数据贯穿整个应用程序。
   * - 1: 启用缓存,数据贯穿整个应用程序,且永不过期(内部设置 staleTime=Infinity)。
   * - 2: 启用缓存,当没有活跃查询时清除缓存(内部设置 staleTime=Infinity)。
   */
  isCache?: -1 | 0 | 1 | 2
  /** 唯一标识符,仅当 isCache 为 -1 时有效 */
  requireId?: number | string
  /** 可选:覆盖 staleTime(当 isCache=1/2 且未显式传入 staleTime 时,默认 Infinity) */
  staleTime?: number
}

QueryInfiniteConfig

分页查询的配置选项:

interface QueryInfiniteConfig<D> extends Omit<InfiniteQueryOptions<PaginatingRecord<D>>, 'queryKey' | 'queryFn' | 'enabled'> {
  isCache?: -1 | 0 | 1 | 2
  /** 页面参数字段名,默认 'pageNo' */
  pageParamKey?: string
  /** 唯一标识符,仅当 isCache 为 -1 时有效 */
  requireId?: number | string
  /** 自定义扁平化函数,默认会把每页的 `records` 扁平到一个数组(若无 records 则当作单项) */
  flatDataSelect?: (data?: InfiniteData<D>) => PaginatingRecordPage<D>
}

MutationOptions

变更操作的配置选项:

type MutationOptions<T, P, V = P> = Omit<UnwrapRef<UseMutationOptions<T, Error, P>>, 'mutationFn' | 'mutationKey'> & {
  /** 将 mutate 的 variables 转换为实际请求的 data */
  transformVariables?: (variables: V) => P
  /** 是否展示全局 loading(与 `showLoading` 配合使用)*/
  showGlobalLoading?: boolean
  /** 展示全局 loading 的方法,返回关闭函数 */
  showLoading?: () => () => void
  /** 是否显示成功通知 */
  showSuccessNotification?: boolean
  /** 是否显示错误通知 */
  showErrorNotification?: boolean
  /** 成功通知的回调函数 */
  onSuccessNotification?: (data?: T, config?: any) => void
  /** 错误通知的回调函数 */
  onErrorNotification?: (error?: Error, config?: any) => void
}

高级用法

自定义 URL 生成

你可以使用 selectUrl 函数来自定义 URL 生成逻辑:

const customUrlQuery = defineQuery<{ id: number, category: string }, any>({
  url: '/api/items/:id',
  method: 'GET',
  selectUrl: (data, options) => {
    // 自定义 URL 生成逻辑
    return `/api/${data?.category}/items/${data?.id}`
  }
})

自定义参数处理

使用 selectParamsselectBody 来自定义参数和请求体的处理:

const customParamsQuery = defineQuery<{ search: string, page: number }, any>({
  url: '/api/search',
  method: 'GET',
  selectParams: (data) => ({
    q: data?.search,
    page: data?.page,
    timestamp: Date.now() // 添加时间戳参数
  })
})

const customBodyQuery = defineQuery<{ name: string, details: any }, any>({
  url: '/api/create',
  method: 'POST',
  selectBody: (data) => ({
    name: data?.name,
    details: JSON.stringify(data?.details) // 将对象序列化为字符串
  })
})

响应数据转换

使用 transformResponse 在数据返回前进行转换:

const userQueryWithTransform = defineQuery<{ id: number }, User, { name: string; emailDomain: string; originalData: User}>({
  url: '/api/user/:id',
  method: 'GET',
  transformResponse: (response, requestData) => {
    const [_, domain = ''] = response.email.split('@')
    return { name: response.name, emailDomain: domain, originalData: response }
  },
})

变量转换

在 Mutation 中使用 transformVariables 转换请求参数:

const userMutation = createUser.useMutation<{ fullName: string; emailAddress: string }>({
  transformVariables: (variables) => ({
    name: variables.fullName,
    email: variables.emailAddress,
  }),
})

await userMutation.mutateAsync({ fullName: '测试用户', emailAddress: '[email protected]' })

表单数据提交

对于文件上传等需要使用 FormData 的场景:

const uploadFile = defineQuery<{ file: File, description: string }, any>({
  url: '/api/upload',
  method: 'POST',
  formData: true // 自动将 body 转换为 FormData
})

// 使用
const { mutate } = uploadFile.useMutation()
mutate({ file: selectedFile, description: '文件描述' })

响应式禁用请求

通过返回 null 可以禁用请求:

<script setup>
import { ref } from 'vue'

const enabled = ref(true)
const id = ref(1)

// 当 enabled 为 false 或 id 为 null 时,请求将被禁用
const { data, isLoading } = getUserById.useQuery(() => 
  enabled.value ? { id: id.value } : null
)
</script>

自动化通知与加载状态

useMutation 支持自动化处理加载状态和通知提醒。你可以通过全局配置或局部配置来开启这些功能。

在 CreateQueryFactory 配置全局方法

import { CreateQueryFactory } from 'cj-query-adapter-vue'

// 1. 创建查询工厂, 配置提示,lodaing的事件; 优先级最低
const defineQuery = CreateQueryFactory(service, queryClient, {
  mutations: {
    onSuccessNotification: (data) => {
      Toast.success('创建成功')
    },
    onErrorNotification: (error) => {
      Toast.fail(error.message || '创建失败')
    },
    showLoading: () => {
      const toast = Toast.loading('加载中...')
      return () => toast.clear()
    }
  }
})

// 2. 什么一个defineQuery service, 优先将中等
// 可以在这个定义这个service提示,lodaing是否展示
const addUser = defineQuery<UserData>({
  url: '/api/user',
  method: 'POST'
}, {
  mutations: {
    showGlobalLoading: true,
    showSuccessNotification: true,
    showErrorNotification: true,
  }
})

// 3. 业务层使用service对应的hook; 优先级最高
const addUserMutation = addUser.useMutation(user, {
  showGlobalLoading: true,
  showSuccessNotification: true,
  showErrorNotification: true,
})

// 4. 执行mutate或者mutateAsync的突变
addUserMutation.mutate(user)

开启加载状态

const createUser = defineQuery<UserData, User>({
  url: '/api/user',
  method: 'POST'
}, {
  mutations: {
    showGlobalLoading: true,
    showLoading: () => {
      const toast = Toast.loading('加载中...')
      return () => toast.clear()
    }
  }
})

开启自动化通知

const createUser = defineQuery<UserData, User>({
  url: '/api/user',
  method: 'POST'
}, {
  mutations: {
    showSuccessNotification: true,
    onSuccessNotification: (data) => {
      Toast.success('创建成功')
    },
    showErrorNotification: true,
    onErrorNotification: (error) => {
      Toast.fail(error.message || '创建失败')
    }
  }
})

Mutation 守卫 (Guards)

useMutation 支持 guards 选项,允许在执行 mutation 之前运行一系列验证或检查函数。

  • 顺序执行:Guards 按数组顺序依次执行。
  • 阻塞执行:如果任何一个 Guard 抛出错误或返回被拒绝的 Promise,Mutation 将终止执行,并触发 onError
  • 错误处理:Guard 抛出的错误会被包装成 GuardError,包含 guardNameoriginalError
import { Guard, GuardError } from 'cj-query-adapter-vue'

const createUser = defineQuery<UserData, User>({
  url: '/api/user',
  method: 'POST'
})

// 定义 Guard
const validateUser: Guard<UserData> = async (user) => {
  if (!user.name) {
    throw new Error('用户名不能为空')
  }
}

const checkPermission: Guard<UserData> = async () => {
   // 模拟权限检查
   const hasPermission = true
   if (!hasPermission) {
      throw new Error('无权操作')
   }
}

// 使用 Guard
const createUserMutation = createUser.useMutation({
  guards: [validateUser, checkPermission],
  onError: (error) => {
    if (error instanceof GuardError) {
      console.log('Guard Error:', error.guardName, error.originalError)
    } else {
      console.log('Mutation Error:', error)
    }
  }
})

// 触发 Mutation
createUserMutation.mutate({ name: '', email: '[email protected]' })

缓存策略

该库提供了灵活的缓存策略,通过 isCache 选项控制:

  • isCache: 0 (默认) - 启用缓存,数据贯穿整个应用程序
  • isCache: 1 - 启用缓存,数据永不过期
  • isCache: 2 - 启用缓存,当没有活跃查询时清除缓存
  • isCache: -1 - 禁用缓存,数据生命周期等同于组件生命周期
// 永不过期的缓存
const neverExpireQuery = defineQuery<{ id: number }, User>({
  url: '/api/user/:id',
  method: 'GET',
  isCache: 1
})

// 组件卸载时清除的缓存
const componentScopedQuery = defineQuery<{ id: number }, User>({
  url: '/api/user/:id',
  method: 'GET',
  isCache: -1
})

微服务名称空间

可以通过声明合并为请求增加自定义字段(如下示例的 nameSpace):

// global.d.ts
import 'cj-query-adapter-vue'

declare module 'cj-query-adapter-vue' {
  interface OtherOptions {
    nameSpace?: string
  }
}
const defineQuery = CreateQueryFactory(async (config) => {
  const host = 'https://api.example.com'
  const url = pathJoin(host, config.nameSpace, config.url)
  const response = await fetch(url, {
    method: config.method,
    body: config.body ? JSON.stringify(config.body) : undefined,
    headers: { 'Content-Type': 'application/json' },
  })
  return response.json()
})

const getUserFromUserService = defineQuery<{ id: number }, User>({
  nameSpace: 'user',
  url: '/api/users/:id',
  method: 'GET',
})

// 请求示例:/user/api/users/1
const user = await getUserFromUserService({ id: 1 })

查询操作

使用 useAction 可以对查询执行各种操作:

// 获取 action 对象
const action = getUserList.useAction()

// 使缓存失效
await action.invalidate()

// 重新获取数据
await action.refetch()

// 从缓存中移除
action.remove()

// 取消请求
await action.cancel()

// 重置查询
await action.reset()

// 获取缓存数据
const cacheData = action.getData()

// 设置缓存数据
action.setData({ records: [], total: 0 })

// 使用特定查询参数操作
await action.invalidate({ pageSize: 10, pageNo: 1 })
const specificData = action.getData({ pageSize: 10, pageNo: 1 })

带默认查询参数的操作

可以为 action 指定默认的查询参数:

// 使用默认查询参数
const action = getUserList.useAction({ pageSize: 10 })

// 操作将使用默认参数,除非显式提供
await action.refetch() // 使用 { pageSize: 10 }
await action.refetch({ pageSize: 20 }) // 使用 { pageSize: 20 }

配置合并策略

cj-query-adapter-vue 采用多层级配置合并机制(CreateQueryFactory 层 -> defineQuery 层 -> useMutation 调用层)。

合并规则

  1. 普通选项: 采用深度合并,右侧优先级更高。
  2. 生命周期钩子 (onMutate):
    • 多个钩子会按顺序依次执行。
    • onMutate 返回的 context 会被深度合并,⚠️不要改context.__loadingClose这个属性;并传递给后续的 onSuccess/onError/onSettled
  3. 普通生命周期钩子 (onSuccess, onError, onSettled):
    • 多个钩子会按顺序异步执行。
    • 不合并返回值。
  4. 通知回调 (onSuccessNotification, onErrorNotification):
    • 采用覆盖原则,只有最后一层定义的配置会生效(右侧优先级更高)。
    • 即使某一层只开启了开关(如 showSuccessNotification: true),它也会触发在更基础层级(如 CreateQueryFactory)定义的通知回调。

useQueryState

useQueryState 用于将外部异步数据源(如接口查询结果)与本地可编辑状态进行安全同步。其行为语义严格对齐 React Hook Form 的 values / reset 机制:

  • 当状态未被用户修改(isDirty === false)时,会持续同步最新的外部数据。
  • 一旦用户修改状态(调用 setState 或修改 modelValue),外部数据将不再覆盖当前状态。
  • 调用 reset() 会将状态重置为最近一次被信任的外部值。
  • 调用 reset(nextValue) 会显式更新基准值,并同步到状态。
  • 提供 modelValue 属性,方便与 Vue 的 v-model 指令配合使用。
<script setup>
import { ref } from 'vue'
import { getUserById } from './api'
import { useQueryState } from 'cj-query-adapter-vue'

const id = ref(1)
const userQuery = getUserById.useQuery(() => ({ id: id.value }))

// 将 query 的数据同步到本地状态
const { state, modelValue, reset, isDirty } = useQueryState(
  () => userQuery.data.value?.name,
  { defaultValue: '张三' }
)
</script>

<template>
  <form @submit.prevent>
    <!-- 方式 1:使用 v-model 绑定 modelValue -->
    <input v-model="modelValue" />
    
    <button type="button" @click="() => reset()">重置</button>
    <p>是否已修改: {{ isDirty }}</p>
  </form>
</template>

测试

包中包含全面的测试用例,涵盖了各种使用场景:

  • use-query.test.ts - 普通查询的基本功能测试
  • use-infinite-query.test.ts - 分页查询功能测试
  • use-mutation.test.ts - 变更操作测试
  • mutation-notification.test.ts - Mutation 通知与加载状态增强功能测试
  • base-options.test.ts - 基础配置合并逻辑测试
  • utils-logic.test.ts - 核心工具函数逻辑测试
  • use-action.test.ts - 查询操作功能测试
  • defineQuery.test.ts - 查询定义功能测试
  • namespace.test.ts - 名称空间功能测试
  • query-state.test.ts - 状态同步(useQueryState)功能测试

你可以参考这些测试用例来理解各种功能的使用方式。