ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Wasp Queries 完全指南:用声明式 query 构建全栈类型安全的读操作

Wasp Queries 完全指南:用声明式 query 构建全栈类型安全的读操作 Wasp Queries 完全指南用声明式 query 构建全栈类型安全的读操作【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/waspWasp 的 Query查询是 Operations 体系中的只读操作用于从服务端拉取数据而绝不修改服务端状态。本文将围绕 web/versioned_docs/version-0.19/data-model/operations/queries.md 这一核心文档展开从声明、实现、调用、错误处理到 API Reference 全链路讲解并引入 waspc/data/Generator/templates/sdk/wasp/client/operations/hooks.ts、waspc/data/Generator/templates/sdk/wasp/client/operations/queries/core.ts 等生成器模板源码作为底层原理佐证。读完本文你将掌握如何在.wasp文件中声明 Query、用 Node.js 实现业务逻辑、在客户端与服务端两处调用、借助useQuery实现响应式数据流并理解 Wasp 自动生成的 HTTP 路由、缓存键与全栈类型安全机制。Query 是什么只读操作在 Wasp 中的地位在 Wasp 中Operations 分为两类Queries查询与Actions写操作。Entity 负责定义数据模型与关系而 Operations 负责操作这些数据。Queries 专门用于读取数据获取一篇博客文章下的全部评论、点赞过某视频的用户列表、根据 ID 查询单个商品信息……这些都是 Query 的典型使用场景。与之相对Actions 用于修改或新增数据。二者在 API 形态上高度相似如果已经熟悉 Actions阅读本指南的大部分内容会觉得重复——官方建议直接跳到 Queries 与 Actions 的差异一节必要时再查阅 API Reference。核心差异有三点职责不同Query 只允许读Action 可以且通常应当修改服务端状态。Wasp 依赖这一约定来做缓存失效所以必须严格遵守。响应性不同Action 不需要响应式可直接调用Query 则常与useQuery钩子配合实现响应式数据。声明关键字不同query与action声明体几乎完全一致唯一的区别是声明名。为什么说 Query 抽象掉了整套 HTTP 层Query 在.wasp文件中声明、用 Node.js 实现。声明之后Wasp 会做两件重要的事生成一个与 Query 同名的服务端 Node.js 函数生成一个与 Query 同名的客户端 JavaScript 函数该函数接收一个可选参数——一个包含任何可序列化数据的对象。Wasp 会把这个对象通过网络发送出去并作为第一个位置参数传给 Query 的实现。这套抽象之所以成立是因为 Wasp 在服务端自动生成了一个 HTTP API 路由处理器route handler由它在内部调用 Query 的 Node.js 实现。因此你无需手动搭建 HTTP API、处理服务端请求分发、编写客户端响应处理与缓存逻辑——只需专注 Query 内部的业务逻辑。从生成器源码看这一机制由 waspc/src/Wasp/Generator/ServerGenerator/OperationsRoutesG.hs路由生成、waspc/src/Wasp/Generator/SdkGenerator/Server/OperationsGenerator.hs 与 waspc/src/Wasp/Generator/SdkGenerator/Client/OperationsGenerator.hs服务端与客户端 SDK 生成共同负责。第一步在.wasp文件中声明 Query创建 Query 需要两步在 Wasp 中用query声明、定义该 Query 的 Node.js 实现。两步完成之后就能在代码的任何位置使用该 Query。下面声明两个 Query一个获取全部任务一个按筛选条件比如任务是否完成获取任务。以main.wasp为例// ... query getAllTasks { fn: import { getAllTasks } from src/queries } query getFilteredTasks { fn: import { getFilteredTasks } from src/queries }需要注意几点Wasp 中 Query 的名字与其实现函数的名字不必一致但官方建议保持一致以免混淆。fn字段用import { ... } from src/queries语法指向src/queries.{js,ts}中的具名导出。此刻实现文件还不存在也没关系先声明高层概念再补实现细节是官方推荐的节奏。src是 Wasp 对src/目录的别名属于 TypeScript/路径别名约定 的一部分。query声明支持的字段字段必填说明fn: ExtImport✅Query 的 Node.js 实现的 import 语句entities: [Entity]—希望在 Query 内部使用的实体列表第二步实现 Query 的 Node.js 函数实现是接收两个位置参数的 Node.js 函数如需使用await可以是async函数。参数名可自由命名官方惯例是args与contextargs类型取决于 Query调用 Query 时传入的数据对象如筛选条件context类型取决于 Query由 Wasp 注入的附加上下文对象包含用户会话信息与实体信息。实现getAllTasks与getFilteredTasks// our database const tasks [ { id: 1, description: Buy some eggs, isDone: true }, { id: 2, description: Make an omelette, isDone: false }, { id: 3, description: Eat breakfast, isDone: false }, ] // You dont need to use the arguments if you dont need them export const getAllTasks () { return tasks } // The args object is something sent by the caller (most often from the client) export const getFilteredTasks (args) { const { isDone } args return tasks.filter((task) task.isDone isDone) }TypeScript 下的类型支持Wasp 会根据.wasp文件中的声明自动生成泛型类型声明getAllTasks会生成GetAllTasks声明getFilteredTasks会生成GetFilteredTasks可从wasp/server/operations导入import { type GetAllTasks, type GetFilteredTasks } from wasp/server/operations type Task { id: number description: string isDone: boolean } // our database const tasks: Task[] [ { id: 1, description: Buy some eggs, isDone: true }, { id: 2, description: Make an omelette, isDone: false }, { id: 3, description: Eat breakfast, isDone: false }, ] // You dont need to use the arguments if you dont need them export const getAllTasks: GetAllTasksvoid, Task[] () { return tasks } // The args object is something sent by the caller (most often from the client) export const getFilteredTasks: GetFilteredTasks PickTask, isDone, Task[] (args) { const { isDone } args return tasks.filter((task) task.isDone isDone) }生成的类型是泛型接收两个可选类型参数InputQuery 函数接收的参数输入 payload类型OutputQuery 函数的返回值输出 payload类型。以GetAllTasksvoid, Task[]为例输入类型是void不接收参数输出类型是Task[]返回任务列表。GetFilteredTasksPickTask, isDone, Task[]则表示期望接收{ isDone: boolean }形状的入参。若不指定类型参数TypeScript 会推断为最宽松的类型输入never、输出unknown。指定Input/Output是完全可选的但官方强烈推荐因为能获得实现内部对参数与返回值的类型支持全栈类型安全——调用 Query 时客户端也能获得类型保障。用satisfies自动推断返回类型如果不想显式标注返回类型可用 TypeScript 的satisfies关键字让编译器自动推断const getFoo (async (_args, context) { const foos await context.entities.Foo.findMany() return { foos, message: Here are some foos!, queriedAt: new Date(), } }) satisfies GetFoo此时 TypeScript 能自动得知context的正确类型以及 Query 返回类型为{ foos: Foo[], message: string, queriedAt: Date }。若完全不需要context甚至可以跳过类型标注与参数const getFoo () {{ name: Foo, date: new Date() }}第三步在客户端与服务端调用 Query客户端调用在客户端从wasp/client/operations导入并直接调用import { getAllTasks, getFilteredTasks } from wasp/client/operations // ... const allTasks await getAllTasks() const doneTasks await getFilteredTasks({ isDone: true })TypeScript 下客户端会自动推断返回值并做 payload 类型检查import { getAllTasks, getFilteredTasks } from wasp/client/operations // TypeScript automatically infers the return values and type-checks // the payloads. const allTasks await getAllTasks() const doneTasks await getFilteredTasks({ isDone: true })这就是 Wasp 的自动全栈类型安全只需在服务端定义处标注 Query 类型客户端代码便自动获知 API 的 payload 类型。调用方式不因 Query 是否经过认证而变化——Wasp 会在后台自动认证已登录用户。服务端调用服务端调用与客户端类似只有两点不同从wasp/server/operations导入而非wasp/client/operations对经过认证的 Query必须传入带user字段的context对象Entities 等其他部分会自动注入无需手动传入。import { getAllTasks, getFilteredTasks } from wasp/server/operations const user // Get an AuthUser object, e.g., from context.user in an operation. // ... const allTasks await getAllTasks({ user }) const doneTasks await getFilteredTasks({ isDone: true }, { user })关于context.user的进一步用法如权限控制、取当前登录用户等可参考 auth 文档的 context.user 小节。使用useQuery钩子让查询响应式在客户端可以用 Wasp 内置的useQuery钩子让 Query 变得响应式。它是对 react-query 的useQuery的轻量封装唯一区别是无需提供 cache key——Wasp 在底层自动处理。import React from react import { useQuery, getAllTasks, getFilteredTasks } from wasp/client/operations const MainPage () { const { data: allTasks, error: error1 } useQuery(getAllTasks) const { data: doneTasks, error: error2 } useQuery(getFilteredTasks, { isDone: true, }) if (error1 ! null || error2 ! null) { return divThere was an error/div } return ( div h2All Tasks/h2 {allTasks allTasks.length 0 ? allTasks.map((task) Task key{task.id} {...task} /) : No tasks} h2Finished Tasks/h2 {doneTasks doneTasks.length 0 ? doneTasks.map((task) Task key{task.id} {...task} /) : No finished tasks} /div ) } const Task ({ description, isDone }: Task) { return ( div p strongDescription: /strong {description} /p p strongIs done: /strong {isDone ? Yes : No} /p /div ) } export default MainPageTypeScript 版本无需注解 Query 返回值类型Wasp 从后端实现自动推断——这正是全栈类型安全的体现客户端类型与服务端类型始终一致。从模板源码看useQuery的实现位于 waspc/data/Generator/templates/sdk/wasp/client/operations/hooks.ts它会校验query必须是函数且带有queryCacheKey属性然后通过makeQueryCacheKey(query, queryFnArgs)构造查询键再转发给 react-query 的useQuery。而makeQueryCacheKey与客户端 Query 函数携带queryCacheKey、queryRoute、entitiesUsed等属性的定义在 waspc/data/Generator/templates/sdk/wasp/client/operations/queries/core.ts。这也是为何 Wasp 能免去手动提供 key 的缘由——每个生成的客户端 Query 函数本身就携带了由相对路由路径构成的缓存键。错误处理默认隐藏细节按需透传出于安全考虑Query 实现中抛出的所有异常发送到客户端时一律以 HTTP 状态码500响应并移除所有其他细节。默认隐藏错误详情有助于避免在网络上意外泄漏敏感信息。若确实希望把额外错误信息传给客户端可以在实现中构造并抛出HttpErrorimport { HttpError } from wasp/server export const getAllTasks async (args, context) { throw new HttpError( 403, // status code You cant do this!, // message { foo: bar } // data ) }规则如下当状态码为4xx时客户端会收到包含对应message与data字段的响应对象并重抛该错误含这两个字段对于其他任何 HTTP 状态码服务端不会转发这些字段以防信息泄漏。在 Query 中使用 Entitycontext.entities 与缓存失效联动大多数情况下Query 中使用的资源是 Entity。要在 Query 中使用 Entity只需把它加入query声明query getAllTasks { fn: import { getAllTasks } from src/queries, entities: [Task] } query getFilteredTasks { fn: import { getFilteredTasks } from src/queries, entities: [Task] }Wasp 会把指定 Entity 注入 Query 的context参数从而获得该 Entity 的 Prisma APIexport const getAllTasks async (args, context) { return context.entities.Task.findMany({}) } export const getFilteredTasks async (args, context) { return context.entities.Task.findMany({ where: { isDone: args.isDone }, }) }context.entities.Task暴露的是 Prisma 的prisma.task即 Prisma CRUD API。注意entities字段同时是缓存失效的基石。Wasp 会通过每个 Action/Query 使用的 Entity 来失效前端 Query 缓存当某个使用同一 Entity 的 Action 被执行时Wasp 会使该 Query 的缓存失效并触发重新拉取详见 Actions 文档中的缓存失效一节。例如createTaskAction 与getTasksQuery 都使用TaskEntity那么执行createTask就会使getTasks的缓存失效。这也是为什么必须遵守“Query 只读、Action 可写”这一约定——它是自动缓存失效机制的前提。API Reference声明 Queryquery声明支持的字段fn: ExtImport必填Query 的 Node.js 实现的 import 语句。entities: [Entity]希望在 Query 内部使用的实体列表用法见上文。示例query getFoo { fn: import { getFoo } from src/queries entities: [Foo] }声明之后即可在代码任意位置客户端或服务端导入使用// Use it on the client import { getFoo } from wasp/client/operations // Use it on the server import { getFoo } from wasp/server/operationsTypeScript 下还会生成可在服务端导入的类型import { type GetFoo } from wasp/server/operations实现 Query实现是接收args与context两个位置参数的 Node.js 函数args调用 Query 时传入的数据如筛选条件contextWasp 注入的上下文对象含用户会话信息与实体信息。TypeScript 下声明为getSomething的 Query 会生成泛型类型GetSomething接收两个可选类型参数Inputargs对象的类型默认值为neverOutputQuery 返回值的类型默认值为unknown。默认值选得尽量宽松。若希望 Query 不接收/不返回任何内容用void作为类型参数。以getFoo为例import { type GetFoo } from wasp/server/operations type Foo // ... export const getFoo: GetFoo{ id: number }, Foo (args, context) { // implementation };这里 Query 期望接收一个带id: number字段的对象args的类型并返回Foo类型的值必须与 Query 实际返回值匹配。useQuery钩子Wasp 的useQuery是对 react-queryuseQuery的轻量封装关键差异是无需提供缓存 key。它接收三个参数queryFn必填Wasp 根据.wasp文件中query声明生成的客户端查询函数queryFnArgs希望传入 Query 的参数对象payloadQuery 的 Node.js 实现会将其作为第一个位置参数接收optionsreact-query 的options对象用于针对该 Query 修改默认行为。若想修改全局默认值可在 client setup 函数中配置。Payload 约束superjson 加持Wasp 底层使用 superjson 做序列化这意味着 payload不限于 JSONbigint、Date、Map、Set以及Prisma.Decimal等 superjson 支持的类型都会被自动处理。在 TypeScript 下只要用自动生成的正确类型标注 Operation编译器就能保证 payload 是合法的即 Wasp 知道如何序列化/反序列化。小结Wasp Query 的价值在于把“声明 → 实现 → 调用”收敛成最小心智负担在.wasp中声明一次Wasp 便自动生成服务端 Node.js 函数、客户端 JS 函数、HTTP 路由处理器与缓存键配合useQuery钩子获得 react-query 的响应式缓存entities字段让 Prisma 数据访问与自动缓存失效开箱即用。从生成器源码可见这一切并非魔法而是 OperationsGenerator.hs 与 hooks.ts 等模板精心设计的产物。若想查看真实项目中的 Query 应用可浏览本仓库 examples 目录 下的示例应用若需要更多上下文可继续阅读 Operations 总览 与 Actions 文档。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表