You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在tRPC中拦截请求,返回localStorage数据替代数据库实时数据?

实现本地/实时数据切换的几种方案

拦截tRPC请求的最优方案

tRPC的**链接(Link)**机制天生支持拦截请求/响应,这比在每个useQuery里写重复逻辑靠谱多了,能统一处理所有请求的切换逻辑。

1. 写一个本地数据拦截链接

这个链接会在请求发出去前判断是否启用本地模式,如果是,直接返回localStorage里的数据,跳过真实API请求:

import { TRPCLink } from '@trpc/client';

export const localStorageLink: TRPCLink<any> = () => {
  return ({ next, op }) => {
    // 从状态管理里拿当前是否启用本地模式(比如用Context或者Zustand)
    const useLocalData = getLocalDataMode(); // 自己实现状态获取逻辑

    if (useLocalData) {
      // 根据接口路径取对应本地数据
      const localData = localStorage.getItem(op.path);
      if (localData) {
        return Promise.resolve({
          result: {
            type: 'data',
            data: JSON.parse(localData),
          },
        });
      }
      // 本地没数据的话,还是走真实请求兜底
    }

    // 不启用本地模式就正常发请求
    return next(op);
  };
};

2. 把链接加到tRPC客户端里

初始化tRPC客户端时,把这个拦截链接放在最前面,确保优先触发:

import { createTRPCNext } from '@trpc/next';
import { localStorageLink } from './localStorageLink';

export const api = createTRPCNext<AppRouter>({
  config() {
    return {
      links: [
        localStorageLink, // 先拦截,再走http链接
        httpBatchLink({
          url: '/api/trpc',
        }),
      ],
    };
  },
  // 其他配置按需添加
});

3. 做个切换状态的管理

用React Context或者状态管理库来存切换按钮的状态,让拦截链接能拿到这个状态。比如用Context:

// context/LocalDataContext.tsx
'use client';

import { createContext, useContext, useState, ReactNode } from 'react';

interface LocalDataContextType {
  useLocalData: boolean;
  toggleLocalData: () => void;
}

const LocalDataContext = createContext<LocalDataContextType | undefined>(undefined);

export function LocalDataProvider({ children }: { children: ReactNode }) {
  const [useLocalData, setUseLocalData] = useState(false);

  const toggleLocalData = () => {
    setUseLocalData(prev => !prev);
  };

  return (
    <LocalDataContext.Provider value={{ useLocalData, toggleLocalData }}>
      {children}
    </LocalDataContext.Provider>
  );
}

export function useLocalData() {
  const context = useContext(LocalDataContext);
  if (!context) {
    throw new Error('useLocalData必须在LocalDataProvider内使用');
  }
  return context;
}

要是用Zustand的话,直接在链接里调用store.getState().useLocalData更方便,不用纠结Hook的使用场景。

替代方案:封装自定义Hook

不想改tRPC链接的话,也可以写个自定义Hook把useQuery包起来,统一处理数据切换:

import { useLocalData } from './context/LocalDataContext';

export function useLocalOrRemoteQuery<T>(
  queryFn: () => { data: T | undefined; refetch: () => Promise<void> },
  localStorageKey: string
) {
  const { useLocalData } = useLocalData();
  const trpcResult = queryFn();

  if (useLocalData) {
    const localData = localStorage.getItem(localStorageKey);
    return {
      ...trpcResult,
      data: localData ? JSON.parse(localData) : trpcResult.data,
    };
  }

  return trpcResult;
}

用的时候直接替换原来的useQuery:

const { data } = useLocalOrRemoteQuery(
  () => api.product.list.useQuery(),
  'product-list'
);

对你原来代码的问题说明

你之前写的onSuccess逻辑是无效的:一是onSuccess是请求成功后才触发,这时候真实API请求已经发出去了,达不到拦截的目的;二是onSuccess的返回值不会修改useQuery的data,所以本地数据不会被用到。

额外提醒

  • 一定要保证localStorage里的数据格式和tRPC返回的完全一致,不然前端渲染会报错;
  • 可以在切换到本地模式时,先把实时数据缓存到localStorage,避免本地没数据的尴尬;
  • 对于修改类的Mutation接口,本地模式下最好直接禁用,或者提示用户切回实时模式再操作,毕竟本地改了也同步不到数据库。

内容的提问来源于stack exchange,提问作者Meriegg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 21:17:45