☰
convex-backend 函数目录入门:从 README 引导到 Query/Mutation 与分页实战
2026/10/13 23:50:19 网站建设 项目流程
  • 数据库
  • 后端

【免费下载链接】convex-backend

The open-source reactive database for app developers

项目地址:https://gitcode.com/gh_mirrors/co/convex-backend
点击查看免费下载

导读

本文围绕npm-packages/private-demos/pagination-visualization/convex/README.md展开,它是 Convex 项目中每个convex/函数目录的标准引导文档:告诉你如何编写带参数校验的 query 查询函数与 mutation 变更函数、如何把函数引用到 React 组件中使用,以及如何用 Convex CLI 推送函数到部署环境。我们将以这份引导文档为主体,结合pagination-visualization演示项目的真实源码与convexnpm 包中的usePaginatedQuery实现,讲透 query/mutation 的完整写法、_generated类型生成机制,以及在此基础上进阶的分页查询原理。读完本文,你将能独立编写、校验、调用和部署自己的 Convex 后端函数。

一、convex/函数目录与引导文档的定位

在 Convex 项目中,你的所有后端函数都集中放在根目录下的convex/文件夹里。pagination-visualization演示项目的目录结构为:

npm-packages/private-demos/pagination-visualization/ ├── convex/ # 后端函数目录 │ ├── _generated/ # CLI 自动生成的类型与 API 引用 │ ├── README.md # 本文所依据的引导文档 │ ├── names.ts # 真实的 query/mutation 函数 │ └── schema.ts # 可选的数据库 schema 定义 ├── src/ # 前端 React 代码 │ ├── App.tsx # 分页可视化界面 │ └── main.tsx └── package.json # 依赖与启动脚本

README.md的第一行 "Welcome to your Convex functions directory!" 明确点出了它的身份——这是脚手架生成的标准引导页,告诉新项目开发者:

  • 在哪里写函数(convex/目录内的任意.ts文件);
  • query 和 mutation 分别长什么样、怎么在 React 里调用;
  • 如何用 CLI 把函数部署到远端。

它提到的两处官方文档入口(docs.convex.dev/functions、docs.convex.dev/database/reading-data)对应着本仓库中 convex 包 等源码注释里反复引用的同名文档。也就是说,这份 README 是整个函数开发流程的"目录页",而本仓库里的源码则是它背后完整的实现。

二、Query 函数:从定义到 React 调用

2.1 带参数校验的 query 定义

README 给出的标准 query 模板如下(原文完整继承):

// convex/myFunctions.ts import { query } from "./_generated/server"; import { v } from "convex/values"; export const myQueryFunction = query({ // Validators for arguments. args: { first: v.number(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Read the database as many times as you need here. const documents = await ctx.db.query("tablename").collect(); // Arguments passed from the client are properties of the args object. console.log(args.first, args.second); // Write arbitrary JavaScript here: filter, aggregate, build derived data, // remove non-public properties, or create new objects. return documents; }, });

这里有几个值得展开的要点:

(1)query来自./_generated/server,而不是从convex包直接导入。_generated/目录由 Convex CLI 自动生成,它在编译期把你的函数与类型系统连接起来。该演示项目的 convex/_generated/server.d.ts 就是生成产物之一,你可以打开查看其中的query、mutation类型签名。

(2)args中的 Validator 是参数校验器。v.number()、v.string()来自convex/values,用于在运行时校验客户端传入的参数。README 注释里说"Validators for arguments"——如果客户端传入的类型不匹配,函数会直接拒绝执行并返回错误。这是 Convex 相比普通 JS 后端的显著差异:参数契约在函数定义处就强制执行。在 npm-packages/convex/src/values 中可以看到完整的校验器体系,包括v.id()、v.array()、v.union()、v.optional()等。

(3)handler的第一个参数ctx是执行上下文。通过ctx.db可以查询数据库。ctx.db.query("tablename").collect()返回表中全部文档;README 注释特别说明"Read the database as many times as you need here"——即 handler 内可以自由多次读取数据库。

2.2 在 React 组件中使用 query

README 给出了对应的 React 端用法:

const data = useQuery(api.myFunctions.myQueryFunction, { first: 10, second: "hello", });

要点是useQuery的第一个参数不是字符串函数名,而是api.myFunctions.myQueryFunction——这个api对象同样来自./_generated/api(如演示项目中的 convex/_generated/api.ts),它提供全类型安全的函数引用:函数名拼错、参数类型不匹配都会在编译期报错。

useQuery是响应式的:当first、second对应的查询结果在服务端发生变化时,组件会自动重新渲染拿到最新数据,无需手动刷新。

三、Mutation 函数:写入数据库的正确姿势

3.1 标准 mutation 模板

README 给出的 mutation 模板(原文完整继承):

// convex/myFunctions.ts import { mutation } from "./_generated/server"; import { v } from "convex/values"; export const myMutationFunction = mutation({ // Validators for arguments. args: { first: v.string(), second: v.string(), }, // Function implementation. handler: async (ctx, args) => { // Insert or modify documents in the database here. // Mutations can also read from the database like queries. const message = { body: args.first, author: args.second }; const id = await ctx.db.insert("messages", message); // Optionally, return a value from your mutation. return await ctx.db.get("messages", id); }, });

与 query 的关键差异:

  • mutation 用于写入数据,ctx.db.insert插入文档、ctx.db.delete删除文档;同时 mutation 内也可以像 query 一样读取数据库(README 注释明示 "Mutations can also read from the database like queries");
  • mutation 的 handler 执行在事务中,要么全部生效要么全部回滚;
  • mutation 可以返回一个值(上例返回刚插入的文档),也可以什么都不返回。

3.2 在 React 中调用 mutation

README 给出的两种调用方式:

const mutation = useMutation(api.myFunctions.myMutationFunction); function handleButtonPress() { // fire and forget, the most common way to use mutations mutation({ first: "Hello!", second: "me" }); // OR // use the result once the mutation has completed mutation({ first: "Hello!", second: "me" }).then((result) => console.log(result), ); }

两种方式的取舍在 README 注释里说得很清楚:

  • fire and forget(直接调用不等待):最常见的方式,适用于不需要关心结果的场景(如"记录一次点击");
  • 等待完成(.then(...)获取返回值):当 UI 需要依赖 mutation 的执行结果(如展示新文档 ID、跳转页面)时使用。

注意useMutation返回的mutation本身就是一个可调用函数,它的签名与后端myMutationFunction的args严格对应。

四、进阶示例:pagination-visualization 如何落地这些模板

引导文档教你写第一个 query/mutation,而演示项目则把它们用到了真实场景。查看 convex/names.ts,可以看到模板的实战变形:

4.1 一个真正的 query:带索引的分页查询

export const getPeople = query({ args: { paginationOpts: paginationOptsValidator, }, handler: async ({ db }, { paginationOpts }) => { return await db .query("people") .withIndex("by_name") .paginate(paginationOpts); }, });

与引导模板相比,这里出现了三个新概念:

  • paginationOptsValidator(来自convex/server):分页参数校验器,定义在 npm-packages/convex/src/server/pagination.ts,结构为{ numItems, cursor, endCursor?, id?, maximumRowsRead?, maximumBytesRead? };
  • .withIndex("by_name"):按 convex/schema.ts 中声明的people表索引查询:
    export default defineSchema({ people: defineTable({ name: v.string(), }).index("by_name", ["name"]), });

    schema 文件是可选的(README 注释 "The schema is entirely optional. You can delete this file and the app will continue to work."),它的价值在于提供更精确的 TypeScript 类型,并声明查询索引;

  • .paginate(paginationOpts):服务端分页 API。它的底层实现会发起1.0/queryPage异步系统调用,携带cursor、endCursor、pageSize、maximumRowsRead、maximumBytesRead等参数(见 query_impl.ts)。

4.2 一组真正的 mutation:seed / addPerson / deletePerson

export const seed = mutation({ args: { names: v.array(v.string()) }, handler: async ({ db }, { names }) => { // Remove all existing rows for await (const row of db.query("people")) { await db.delete(row._id); } for (const name of names) { await db.insert("people", { name }); } }, }); export const addPerson = mutation({ args: { name: v.string() }, handler: async ({ db }, { name }) => { await db.insert("people", { name }); }, }); export const deletePerson = mutation({ args: { id: v.id("people") }, handler: async ({ db }, { id }) => { await db.delete(id); }, });

这三个 mutation 演示了引导模板之外的能力:

  • v.array(v.string())表示参数是字符串数组;
  • v.id("people")表示参数必须是people表的合法文档 ID,类型校验直接与 schema 关联;
  • 在 mutation 中用for await遍历并删除全部旧数据,实现了"先清空再灌入"的 seed 逻辑——这正是引导文档所说"mutation 内也可以读取数据库"的实际应用。

在 src/App.tsx 中,它们通过useMutation(api.names.addPerson)、useMutation(api.names.deletePerson)、useMutation(api.names.seed)接入 UI,点击 Seed 按钮会随机抽取 20 个名字灌入数据库。

五、深入分页:引导模板之外的完整链路

pagination-visualization的核心价值是把"分页过程中每一页到底发生了什么"可视化出来。这需要理解前端 hook 与服务端 API 的配合。

5.1 客户端:usePaginatedQuery与usePaginatedQueryInternal

前端核心代码在 src/App.tsx:

const { user: { results, status, loadMore }, internal, } = usePaginatedQueryInternal( api.names.getPeople, {}, { initialNumItems: 5, [includePage]: true }, );

这里用到的是convex/react导出的内部版本usePaginatedQueryInternal(usePaginatedQuery的底层实现,见 use_paginated_query.ts)。普通项目用公开的usePaginatedQuery即可:

const { results, status, isLoading, loadMore } = usePaginatedQuery( api.messages.list, { channel: "#general" }, { initialNumItems: 5 }, );

usePaginatedQuery会做三件事:

  1. 注入分页参数:自动在 args 里加上paginationOpts: { numItems, cursor, id },其中cursor: null表示从第一页开始(见源码 L231-L237);
  2. 拼接所有页:把已加载的每一页page结果拼成一个完整列表results;
  3. 管理状态机:返回status(LoadingFirstPage/CanLoadMore/LoadingMore/Exhausted)和loadMore(n)回调。

5.2 分页状态机与异常恢复

从 use_paginated_query.ts 的状态构造逻辑可以看到完整的状态定义:

statusisLoading含义loadMore 行为
LoadingFirstPagetrue正在加载第一页noop
CanLoadMorefalse还有更多数据可加载发起下一页请求
LoadingMoretrue正在加载后续页noop
Exhaustedfalse已到列表末尾noop
Error(内部版本)false查询出错noop

另外,当服务端返回InvalidCursor错误时(例如分页所依赖的数据在读取过程中发生了变化),hook 会主动重置分页状态从头开始(见源码 L290-L310),并在 logger 中打印警告。每个分页会话还会携带一个自增的paginationId作为 "cache-buster",保证新会话不会错误复用旧的订阅结果(见 L452-L480)。

5.3 服务端:PaginationResult与页面拆分

服务端.paginate()的返回结构PaginationResult定义在 pagination.ts:

interface PaginationResult<T> { page: T[]; // 本页结果 isDone: boolean; // 是否已到末尾 continueCursor: Cursor; // 下一页的游标 splitCursor?: Cursor | null; // 拆分本页为两半的游标 pageStatus?: "SplitRecommended" | "SplitRequired" | null; }

其中pageStatus是响应式分页的进阶机制:当一页读取的数据量过大时,服务端可能返回SplitRecommended(建议拆分)甚至SplitRequired(页面可能不完整,必须拆分)。客户端检测到这些信号后,会调用splitQuery把这一页拆成(cursor, splitCursor]和(splitCursor, continueCursor]两个半页并行加载(见 L60-L96)。

pagination-visualization的 App.tsx 正是把这些"页面"的边界、每个分页参数(numItems、cursor、endCursor、id等)逐条渲染出来,并用颜色标记游标,让你直观看到响应式数据变化时分页如何自动调整——这是理解endCursor参数作用(确保数据变化时页面之间不出现空隙,见 pagination.ts)的最佳实验场。

六、用 Convex CLI 推送与运行

README 的最后一段说明了 CLI 的使用方式:"Use the Convex CLI to push your functions to a deployment. See everything the Convex CLI can do by runningnpx convex -hin your project root directory. To learn more, launch the docs withnpx convex docs."

在pagination-visualization项目中实际运行:

npx convex dev --start 'vite --open'

这是 package.json 中定义的dev脚本:convex dev会持续监听convex/目录,把函数同步到本地开发部署,并自动生成_generated/文件;--start 'vite --open'则同时启动 Vite 开发服务器并打开浏览器。

常用 CLI 命令速查:

命令作用
npx convex -h查看全部 CLI 命令
npx convex docs在本地启动官方文档
npx convex dev本地开发:监听函数变更、同步部署、生成_generated
npx convex dev --once单次同步后退出(CI 场景常用)
npx convex deploy推送函数到生产部署

README 特别提示要在project root directory(项目根目录)运行这些命令——因为 CLI 需要读取根目录下的convex.json与package.json来确定项目配置。

七、快速上手清单

结合引导文档与演示项目,一个完整的新手路径如下:

  1. 初始化项目:创建convex/目录,放入README.md(脚手架会生成)与函数文件;
  2. 定义 schema(可选):在 convex/schema.ts 中用defineSchema/defineTable声明表和索引;
  3. 编写函数:query 用query({ args: {...}, handler }),写入用mutation({...}),参数一律用v校验器声明;
  4. 跑一次 CLI:npx convex dev生成_generated/,此后 import./_generated/server、../convex/_generated/api均获得类型安全;
  5. 接入前端:useQuery(api.xxx, args)读、useMutation(api.xxx)写;列表加载用usePaginatedQuery配合initialNumItems与loadMore;
  6. 验证效果:对照pagination-visualization的可视化界面(loadMore(5)、Seed 按钮、页面边界渲染),确认每个status的流转是否符合预期。

结语

convex/README.md虽然只是一份脚手架引导,但它浓缩了 Convex 函数开发的全部核心动作:用 Validator 声明参数契约、用ctx.db读写数据、用_generated获得类型安全、用 CLI 完成部署。在此基础上,pagination-visualization演示项目把 query/mutation 模板延展成了带索引、带分页、带响应式可视化的完整应用,而 use_paginated_query.ts 与 pagination.ts 则揭示了游标、页面拆分与状态机背后的实现细节。从模板到实战,再到源码级原理,这条路径正是理解 Convex 后端开发的最佳入口。

  • 数据库
  • 后端

【免费下载链接】convex-backend

The open-source reactive database for app developers

项目地址:https://gitcode.com/gh_mirrors/co/convex-backend
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询