跳至主内容
版本:11.x

useUtils

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

useUtils 是一个钩子函数,提供访问辅助工具的权限,用于管理通过 @trpc/react-query 执行查询的缓存数据。这些辅助工具本质上是 @tanstack/react-queryqueryClient 方法的轻量封装。若需获取比本文更深入的 useContext 辅助工具选项和使用模式,我们将链接至相应的 @tanstack/react-query 文档供您参考。

备注

10.41.0 版本之前,此钩子名为 useContext()(在可预见的未来仍保留此别名)

用法

useUtils 返回一个包含你所有路由器中可用查询的对象。其使用方式与 trpc 客户端对象相同。定位到具体查询后,即可访问其查询辅助工具。例如,假设你有一个包含 all 查询的 post 路由器:

server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;
server.ts
ts
// @filename: server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
 
const t = initTRPC.create();
 
const appRouter = t.router({
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
}),
});
 
export type AppRouter = typeof appRouter;

现在在组件中,当我们导航 useUtils 提供的对象并定位到 post.all 查询时,即可访问查询辅助工具!

MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useUtils();
utils.post.all.f;
                  
// [...]
}
MyComponent.tsx
tsx
function MyComponent() {
const utils = trpc.useUtils();
utils.post.all.f;
                  
// [...]
}

辅助函数

以下是可通过 useUtils 访问的辅助工具。下表将说明每个 tRPC 辅助工具封装了哪个 @tanstack/react-query 辅助方法,每个 react-query 方法均会链接到其对应文档/指南:

tRPC helper wrapper@tanstack/react-query helper method
fetchqueryClient.fetchQuery
prefetchqueryClient.prefetchQuery
fetchInfinitequeryClient.fetchInfiniteQuery
prefetchInfinitequeryClient.prefetchInfiniteQuery
ensureDataqueryClient.ensureData
invalidatequeryClient.invalidateQueries
refetchqueryClient.refetchQueries
cancelqueryClient.cancelQueries
setDataqueryClient.setQueryData
setQueriesDataqueryClient.setQueriesData
getDataqueryClient.getQueryData
setInfiniteDataqueryClient.setInfiniteQueryData
getInfiniteDataqueryClient.getInfiniteData
setMutationDefaultsqueryClient.setMutationDefaults
getMutationDefaultsqueryClient.getMutationDefaults
isMutatingqueryClient.isMutating

❓ 找不到我需要的函数!

@tanstack/react-query 包含许多尚未集成到 tRPC 上下文中的函数。如需未列出的函数,欢迎提交功能请求

在此期间,你可以直接从 @tanstack/react-query 导入并使用这些函数。我们还提供了 getQueryKey,可在使用这些函数时获取筛选器上的正确 queryKey。

代理客户端

除上述 react-query 辅助工具外,上下文还暴露了你的 tRPC 代理客户端。这让你无需创建额外的原生客户端即可通过 async/await 调用过程。

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useUtils();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const [apiKey, setApiKey] = useState();
const utils = trpc.useUtils();
return (
<Form
handleSubmit={async (event) => {
const apiKey = await utils.client.apiKey.create.mutate(event);
setApiKey(apiKey);
}}
>
...
</Form>
);
}

查询失效

通过 invalidate 辅助工具使查询失效。invalidate 是一个特殊辅助工具,与其他工具不同,它在路由器映射的每个层级都可用。这意味着你可以在单个查询、整个路由器或所有路由器上运行 invalidate。下文将详细说明。

使单个查询失效

你可以使与单个过程相关的查询失效,甚至可以根据传入的输入进行过滤,以避免不必要的后端调用。

示例代码

tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}

使整个路由器失效

除了单个查询外,也可以使整个路由器中的查询失效。

示例代码

Backend code
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
server/routers/_app.ts
tsx
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
export const appRouter = t.router({
// sub Post router
post: t.router({
all: t.procedure.query(() => {
return {
posts: [
{ id: 1, title: 'everlong' },
{ id: 2, title: 'After Dark' },
],
};
}),
byId: t.procedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
post: { id: input?.id, title: 'Look me up!' },
};
}),
edit: t.procedure
.input(z.object({ id: z.number(), title: z.string() }))
.mutation(({ input }) => {
return { post: { id: input.id, title: input.title } };
}),
}),
// separate user router
user: t.router({
all: t.procedure.query(() => {
return { users: [{ name: 'Dave Grohl' }, { name: 'Haruki Murakami' }] };
}),
}),
});
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}
tsx
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useUtils();
const invalidateAllQueriesAcrossAllRouters = () => {
// 1️⃣
// All queries on all routers will be invalidated 🔥
utils.invalidate();
};
const invalidateAllPostQueries = () => {
// 2️⃣
// All post queries will be invalidated 📭
utils.post.invalidate();
};
const invalidatePostById = () => {
// 3️⃣
// All queries in the post router with input {id:1} invalidated 📭
utils.post.byId.invalidate({ id: 1 });
};
// Example queries
trpc.user.all.useQuery(); // Would only be validated by 1️⃣ only.
trpc.post.all.useQuery(); // Would be invalidated by 1️⃣ & 2️⃣
trpc.post.byId.useQuery({ id: 1 }); // Would be invalidated by 1️⃣, 2️⃣ and 3️⃣
trpc.post.byId.useQuery({ id: 2 }); // would be invalidated by 1️⃣ and 2️⃣ but NOT 3️⃣!
// [...]
}

每次变更时使整个缓存失效

精确追踪变更应使哪些查询失效非常困难,因此在任何变更时使_整个缓存_失效是一种务实的解决方案。由于我们支持请求批处理,此失效操作将通过单一请求重新获取当前页面的所有查询。

我们已添加功能来支持此场景:

ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});
ts
export const trpc = createTRPCReact<AppRouter, SSRContext>({
overrides: {
useMutation: {
/**
* This function is called whenever a `.useMutation` succeeds
**/
async onSuccess(opts) {
/**
* @note that order here matters:
* The order here allows route changes in `onSuccess` without
* having a flash of content change whilst redirecting.
**/
// Calls the `onSuccess` defined in the `useQuery()`-options:
await opts.originalFn();
// Invalidate all queries in the react-query cache:
await opts.queryClient.invalidateQueries();
},
},
},
});

附加选项

除查询辅助工具外,useUtils 返回的对象还包含以下属性:

ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}
ts
interface ProxyTRPCContextProps<TRouter extends AnyRouter, TSSRContext> {
/**
* The `TRPCClient`
*/
client: TRPCClient<TRouter>;
/**
* The SSR context when server-side rendering
* @default null
*/
ssrContext?: TSSRContext | null;
/**
* State of SSR hydration.
* - `false` if not using SSR.
* - `prepass` when doing a prepass to fetch queries' data
* - `mounting` before TRPCProvider has been rendered on the client
* - `mounted` when the TRPCProvider has been rendered on the client
* @default false
*/
ssrState?: SSRState;
/**
* Abort loading query calls when unmounting a component - usually when navigating to a new page
* @default false
*/
abortOnUnmount?: boolean;
}