深入理解 Convex 函数体系:基于 convex-backend 仓库的 Query / Mutation 编写与 React 集成指南
2026/9/23 11:39:42 网站建设 项目流程
  • 数据库
  • 后端

【免费下载链接】convex-backend

The open-source reactive database for app developers

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

导读

Convex 是一个为应用开发者设计的开源响应式数据库与后端平台,函数(Functions)是它的核心编程模型:开发者用 TypeScript 编写查询(Query)、修改(Mutation)与动作(Action),即可获得自动的类型安全、参数校验、实时订阅与数据库读写能力。本文以本仓库中npm-packages/private-demos/components-legacy/convex/README.md的函数模板文档为骨架,结合仓库内真实的函数实现、_generated生成代码与convexnpm 包源码,系统讲解 Convex 函数从定义、校验到前端调用的完整链路,并演示其在组件化 demo 中的实际用法,帮助读者直接照搬到自己的 Convex 项目中。

Convex 函数全景:Query、Mutation 与 Action

Convex 的服务器端代码全部集中在项目的convex/目录下。每个被导出的函数会自动成为部署 API 的一部分,前端通过api对象按"模块.函数名"的路径引用它。函数按能力分为三类:

函数类型能否读库能否写库能否执行任意 JavaScript(含副作用)典型用途
query仅确定性代码读取并派生数据,供客户端实时订阅
mutation仅确定性代码写入数据库,业务状态变更
action间接(通过runQuery/runMutation间接调用第三方 API、执行不确定/有副作用逻辑

从 convex/_generated/server.js 的生成代码可以看出,这三类函数在公共 API 之外,还有仅供服务端互相调用的内部版本internalQueryinternalMutationinternalAction,以及用于响应 HTTP 请求的httpAction。该文件是npx convex dev自动生成的,文件头明确提示"THIS CODE IS AUTOMATICALLY GENERATED",开发者不应手工修改它。

编写第一个 Query 函数

关联文档给出的 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({...})是工厂函数:它接收一个配置对象,配置对象必须包含args(参数校验器)和handler(函数实现体)两个字段。querymutationaction一样,都从./_generated/server导入——这是类型安全的入口,它把泛型实现queryGeneric/mutationGeneric/actionGeneric与当前部署的 schema 类型绑定在一起。

  2. args声明参数契约v.number()v.string()来自convex/values。Convex 会在每次调用时对传入参数做运行时校验,非法参数直接拒绝执行,同时校验器也承担 TypeScript 类型推导的职责,让handler里的args.first具备精确类型。

  3. ctx.db是数据库访问句柄ctx.db.query("tablename")按表名查询,.collect()把结果集物化为数组。Query 函数体内可以多次读取数据库,也可以做任意确定性变换(过滤、聚合、构造派生数据、剔除非公开字段、创建新对象),最终返回值会通过响应式订阅通道推送给客户端。

响应式:Query 的本质是"订阅"

query 与普通 HTTP 接口最大的区别在于响应式。前端通过useQuery订阅某个 query 后,只要 query 依赖的表发生变化,Convex 后端会重新执行该函数并把最新结果推送到客户端——开发者无需手写缓存失效或轮询。这种机制在本仓库有完整的 Rust 后端支撑,例如crates/database/src/subscription.rscrates/database/src/query目录下即包含订阅追踪与查询执行相关实现。

在真实 demo 中读取数据

本仓库的组件化 demo 在 convex/messages.ts 中给出了一个带返回类型标注的 query 实例:

import { Doc } from "./_generated/dataModel"; export const list = query({ args: {}, handler: async (ctx): Promise<Doc<"messages">[]> => { const result = await ctx.runQuery(waitlist.index.sayGoodbyeFromQuery, {}); console.log(result); return await ctx.db.query("messages").collect(); }, });

这里展示了两个进阶能力:通过Promise<Doc<"messages">[]>显式声明返回类型(Doc<"messages">_generated/dataModel根据 schema 自动生成),以及通过ctx.runQuery在函数内部调用另一个组件暴露的 query——实现函数间(跨组件)的服务端调用。

Validator:参数校验与类型推导的底层原理

关联文档把args里的v.number()v.string()注释为 "Validators for arguments"。这套 validator 体系并不只是 TypeScript 层的玩具,它在运行时真正生效,其实现位于 npm 包源码 convex/src/values/validators.ts 与 convex/src/values/validator.ts。

从源码结构看,所有校验器都继承自BaseValidator抽象类,该类维护了三个关键成员:

  • type:仅供 TypeScript 使用的类型占位,用于把校验器映射为对应的 TS 类型;
  • isOptional:标记该字段是否可选("required""optional");
  • isConvexValidator:恒为true的运行时标记,用于区分"校验器对象"与"普通对象"。

isValidator()函数正是通过检查v?.isConvexValidator === true来判断一个值是否为合法校验器(见 validator.ts)。asObjectValidator()则允许把一个"属性到校验器的映射"自动包成v.object(...)

demo 的 schema 展示了 validator 在表结构定义中的另一种用法:

// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ messages: defineTable({ author: v.string(), body: v.string(), }), notes: defineTable({ text: v.string(), }), });

defineSchema/defineTable来自convex/server,这里v.string()定义的是列的字段类型。值得注意的是源码中throwUndefinedValidatorError会专门提示 "A validator is undefined ... often caused by circular imports",提醒开发者不要在模块顶层出现循环导入导致 validator 求值失败。

编写 Mutation:写入与返回结果

关联文档给出的 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); }, });

Mutation 的要点:

  1. ctx.db.insert(table, doc)写入:第一个参数是表名,第二个是待插入文档;返回新文档的Id
  2. mutation 可以同时读写:与 query 一样支持ctx.db.query(...)读取,且所有操作处于同一个事务中,要么全部成功要么全部回滚。
  3. 返回值可选ctx.db.get(table, id)按 ID 取回文档后返回,客户端可拿到Promise化的结果。

demo 中的sendmutation 是更精简的等价写法:

export const send = mutation({ args: { body: v.string(), author: v.string(), }, handler: async (ctx, { body, author }) => { const message = { body, author }; await ctx.db.insert("messages", message); }, });

这里用了解构写法({ body, author })直接提取参数,写入后不返回值(前端默认"fire and forget")。同一文件中的scheduleSendWaitlistMessage还展示了 mutation 中调用ctx.scheduler.runAfter(30 * 1000, waitlist.index.scheduleMessage, {})定时调度另一个函数的能力,以及通过ctx.db.system.query("_scheduled_functions")读取系统表验证调度结果。

事务回滚的一个真实测试场景

demo 的testPartialRollbackmutation 专门演示了跨组件调用时的部分回滚语义:

await ctx.runMutation(waitlist.index.writeSuccessfully, { text: "hello" }); try { await ctx.runMutation(waitlist.index.writeThenFail, { text: "world" }); } catch (e) { console.log("caught error", e); } const result = await ctx.runQuery(waitlist.index.latestWrite, {});

它先让组件成功写入一次,再故意触发一次会失败的写入,捕获错误后查询最新写入内容——验证失败的 mutation 是否已回滚、成功的那次是否保留,这正是 mutation 事务性在组件嵌套场景下的行为测试。

React 集成:useQuery 与 useMutation

关联文档给出了函数在 React 组件中的两种调用方式。

用 useQuery 订阅数据

const data = useQuery(api.myFunctions.myQueryFunction, { first: 10, second: "hello", });
  • useQuery的第一个参数是函数引用api.myFunctions.myQueryFunction,第二个参数是与该函数args完全匹配的参数对象;
  • 返回值data的类型由函数返回类型推导而来;
  • 由于 query 是响应式的,data会随数据库变化自动更新,组件无需关心请求时机。

useQuery的真实实现在 convex/src/react/client.ts(约 L922 处),它要求传入的参数必须是FunctionReference<"query">类型——这正是api对象(anyApi,见 convex/_generated/api.js)提供的类型安全引用。同类 hook 还包括useQueries(批量订阅多个 query)、usePaginatedQuery(分页查询)、useMutationuseActionuseConvex等,均在convex/src/react目录下。

用 useMutation 触发写入

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), ); }

useMutation返回一个"触发函数",它有两种调用语义:

  1. fire and forget:直接mutation({...})发起调用,不关心结果,这是最常见的方式,适合按钮点击等事件处理;
  2. 等待结果:调用后拿到 Promise,通过.then((result) => ...)await使用 mutation 的返回值(对应 mutation handler 的return语句)。

由于useMutation返回的函数是稳定的引用(见 client.ts L1084 处实现),它可以安全地放进useEffect依赖数组或作为 props 传递,不会因组件重渲染而失效。

从模板到真实项目:组件化 demo 的函数调用链

关联文档所在的项目components-legacy是一个专门用于测试"组件 API 导入"的 demo。其 convex.config.ts 展示了组件注册方式:

import { defineApp } from "convex/server"; import waitlist from "../examples/waitlist@name-with-dashes/convex.config.js"; import nestedComponent from "./nested-component/convex.config.js"; const app = defineApp(); app.use(waitlist, { name: "waitlist" }); app.use(waitlist, { name: "waitlist2" }); app.use(nestedComponent); export default app;

同一个waitlist组件被注册了两次(waitlistwaitlist2),外加一个嵌套组件nestedComponent。而 messages.ts 通过const waitlist = components.waitlist satisfies ComponentApi拿到类型安全的组件句柄后,即可用ctx.runQueryctx.runMutationctx.runActionctx.scheduler.runAfter跨组件调用其内部函数——这正是"函数模板文档 + 组件体系"结合后的实际形态,也印证了模板中ctx参数在多函数编排中的核心地位。

CLI 工作流:从开发到部署

关联文档最后给出两条最常用的 CLI 命令:

  • npx convex dev:本地开发模式。启动后会持续监听convex/目录,自动重新生成_generated代码(server.tsapi.tsdataModel.ts等)并同步函数到本地部署,配合前端框架实现热更新开发体验;
  • npx convex -h:在项目根目录运行,列出 Convex CLI 的全部可用命令;
  • npx convex docs:在本地启动官方文档。

在本仓库中,_generated文件(如 convex/_generated/server.js)头部都明确写着 "To regenerate, runnpx convex dev",说明生成文件与 CLI 的绑定关系。若使用其他框架,可用npx convex dev --help查看对接参数;部署到线上环境的命令是npx convex deploy

小结:一套模板,三种能力

关联文档的这份函数目录模板,浓缩了 Convex 开发中最核心的三件事:

  1. query({ args, handler })定义可订阅的读函数,参数由 validator 校验并推导类型,ctx.db提供事务性读取;
  2. mutation({ args, handler })定义写函数,支持读写混合与返回值;
  3. 在 React 中用useQuery订阅、用useMutation触发,获得端到端类型安全与实时数据同步。

在此基础上,本仓库的components-legacydemo 展示了函数在组件体系下的进阶用法:跨组件runQuery/runMutation/runAction、定时调度、事务回滚验证等。无论你是在一个简单的单表 demo 上起步,还是在复杂的多组件应用中编排逻辑,这套模板都是进入 Convex 函数世界最直接的入口。

  • 数据库
  • 后端

【免费下载链接】convex-backend

The open-source reactive database for app developers

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

相关推荐

上一篇:FlyingCarpet:无缝穿梭数据迁移的魔法地毯
下一篇:超实用!babel-plugin-preval构建时预编译实战指南:从原理到性能优化

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

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

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

立即咨询