feat(query): integrate @tanstack/react-query for improved data fetching and state management

This commit is contained in:
2026-03-02 12:52:22 +08:00
parent d0e47e2d87
commit dc95e1ec60
5 changed files with 89 additions and 45 deletions

View File

@@ -25,19 +25,36 @@ src/
```
pages/page-app/
├── components/ # 模块专属组件
├── store/ # 模块状态管理
── module/ # 模块功能函数
├── hooks/ # 模块 React Query hooksAPI 查询封装)
── modules/ # 模块功能函数UI 组件、工具函数等)
└── store/ # 模块状态管理Zustand
```
### hooks/ 文件夹说明
每个模块的 `hooks/` 文件夹用于封装与该模块相关的 React Query hooks
- **use-api-query.ts**: 使用 `@tanstack/react-query``useQuery` 封装 API 调用
- 定义 `queryKeys` 常量用于缓存标识
- 封装 `useQuery` hooks 用于数据获取GET 请求)
- 封装 `useMutation` hooks 用于数据修改POST/PUT/DELETE 请求)
- 支持预取prefetch和无限滚动infinite query
- **index.ts**: 导出模块所有 hooks便于统一导入使用
### 状态和数据获取
- **@tanstack/react-query** 用于数据获取、缓存和状态管理
- 在模块的 `hooks/` 文件夹中封装 API 调用
- QueryClient 实例位于 `src/modules/query.ts`
-`src/routes/__root.tsx` 中通过 `QueryClientProvider` 提供
- **Zustand** 用于全局状态管理
- **@kevisual/query** 用于数据获取QueryClient 实例位于 `src/modules/query.ts`
- **@kevisual/query** 用于底层 API 请求封装
- **React Hook Form** 用于表单管理
## 核心依赖
- **@base-ui/react**: Headless UI 基础组件
- **@tanstack/react-query**: 数据获取、缓存和状态管理(配合 hooks/ 使用)
- **@tanstack/react-router**: 基于 TanStack Router 插件的文件路由
- **class-variance-authority**: 基于变体的样式系统
- **clsx + tailwind-merge**: 通过 `cn()` 提供 className 工具函数

View File

@@ -21,6 +21,7 @@
"@kevisual/api": "^0.0.60",
"@kevisual/context": "^0.0.8",
"@kevisual/router": "0.0.84",
"@tanstack/react-query": "^5.90.21",
"@tanstack/react-router": "^1.163.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -46,8 +47,8 @@
"@kevisual/types": "^0.0.12",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-router-devtools": "^1.163.3",
"@tanstack/router-plugin": "^1.163.3",
"@types/node": "^25.3.2",
"@tanstack/router-plugin": "^1.164.0",
"@types/node": "^25.3.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",

66
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ importers:
'@kevisual/router':
specifier: 0.0.84
version: 0.0.84
'@tanstack/react-query':
specifier: ^5.90.21
version: 5.90.21(react@19.2.4)
'@tanstack/react-router':
specifier: ^1.163.3
version: 1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -77,16 +80,16 @@ importers:
version: 0.0.12
'@tailwindcss/vite':
specifier: ^4.2.1
version: 4.2.1(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
version: 4.2.1(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
'@tanstack/react-router-devtools':
specifier: ^1.163.3
version: 1.163.3(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.163.3)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-plugin':
specifier: ^1.163.3
version: 1.163.3(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
specifier: ^1.164.0
version: 1.164.0(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
'@types/node':
specifier: ^25.3.2
version: 25.3.2
specifier: ^25.3.3
version: 25.3.3
'@types/react':
specifier: ^19.2.14
version: 19.2.14
@@ -95,7 +98,7 @@ importers:
version: 19.2.3(@types/react@19.2.14)
'@vitejs/plugin-react':
specifier: ^5.1.4
version: 5.1.4(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
version: 5.1.4(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))
dotenv:
specifier: ^17.3.1
version: 17.3.1
@@ -113,7 +116,7 @@ importers:
version: 5.9.3
vite:
specifier: v8.0.0-beta.15
version: 8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
version: 8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
packages:
@@ -853,6 +856,14 @@ packages:
resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==}
engines: {node: '>=20.19'}
'@tanstack/query-core@5.90.20':
resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==}
'@tanstack/react-query@5.90.21':
resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==}
peerDependencies:
react: ^18 || ^19
'@tanstack/react-router-devtools@1.163.3':
resolution: {integrity: sha512-42VMkV/2Z8ro7xzblPBRNZIEmCNXMzm2jD68G52p2qhjXm38wGpg46qneAESN9FtTQeVWk5aSXs47/jt7lkzmw==}
engines: {node: '>=20.19'}
@@ -892,12 +903,12 @@ packages:
csstype:
optional: true
'@tanstack/router-generator@1.163.3':
resolution: {integrity: sha512-i2rWRtqY/yCYUDXva1li4zeDP20oFjMt/wh9RnGJCrKSLWrvEGnxAOSyXgiOsoJnU96TTQ0mUDbGfXsSTupeZQ==}
'@tanstack/router-generator@1.164.0':
resolution: {integrity: sha512-Uiyj+RtW0kdeqEd8NEd3Np1Z2nhJ2xgLS8U+5mTvFrm/s3xkM2LYjJHoLzc6am7sKPDsmeF9a4/NYq3R7ZJP0Q==}
engines: {node: '>=20.19'}
'@tanstack/router-plugin@1.163.3':
resolution: {integrity: sha512-JOUYuUX2N9ZHnmkmvmiGzXGbkvrur/5BfW/+vpiZzuifSyvdc0XsfwkTpjvwWx9ymp4ZshSVKiQQKQi09YweIw==}
'@tanstack/router-plugin@1.164.0':
resolution: {integrity: sha512-cZPsEMhqzyzmuPuDbsTAzBZaT+cj0pGjwdhjxJfPCM06Ax8v4tFR7n/Ug0UCwnNAUEmKZWN3lA9uT+TxXnk9PQ==}
engines: {node: '>=20.19'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
@@ -943,8 +954,8 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/node@25.3.2':
resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==}
'@types/node@25.3.3':
resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==}
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
@@ -2169,15 +2180,22 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
'@tailwindcss/vite@4.2.1(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
'@tailwindcss/vite@4.2.1(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
tailwindcss: 4.2.1
vite: 8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
vite: 8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
'@tanstack/history@1.161.4': {}
'@tanstack/query-core@5.90.20': {}
'@tanstack/react-query@5.90.21(react@19.2.4)':
dependencies:
'@tanstack/query-core': 5.90.20
react: 19.2.4
'@tanstack/react-router-devtools@1.163.3(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.163.3)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@tanstack/react-router': 1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -2226,7 +2244,7 @@ snapshots:
optionalDependencies:
csstype: 3.2.3
'@tanstack/router-generator@1.163.3':
'@tanstack/router-generator@1.164.0':
dependencies:
'@tanstack/router-core': 1.163.3
'@tanstack/router-utils': 1.161.4
@@ -2239,7 +2257,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-plugin@1.163.3(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
'@tanstack/router-plugin@1.164.0(@tanstack/react-router@1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
@@ -2248,7 +2266,7 @@ snapshots:
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
'@tanstack/router-core': 1.163.3
'@tanstack/router-generator': 1.163.3
'@tanstack/router-generator': 1.164.0
'@tanstack/router-utils': 1.161.4
'@tanstack/virtual-file-routes': 1.161.4
chokidar: 3.6.0
@@ -2256,7 +2274,7 @@ snapshots:
zod: 3.25.76
optionalDependencies:
'@tanstack/react-router': 1.163.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
vite: 8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
vite: 8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
transitivePeerDependencies:
- supports-color
@@ -2304,7 +2322,7 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
'@types/node@25.3.2':
'@types/node@25.3.3':
dependencies:
undici-types: 7.18.2
@@ -2316,7 +2334,7 @@ snapshots:
dependencies:
csstype: 3.2.3
'@vitejs/plugin-react@5.1.4(vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
'@vitejs/plugin-react@5.1.4(vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
@@ -2324,7 +2342,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-rc.3
'@types/babel__core': 7.20.5
react-refresh: 0.18.0
vite: 8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
vite: 8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)
transitivePeerDependencies:
- supports-color
@@ -2783,7 +2801,7 @@ snapshots:
dependencies:
react: 19.2.4
vite@8.0.0-beta.15(@types/node@25.3.2)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0):
vite@8.0.0-beta.15(@types/node@25.3.3)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0):
dependencies:
'@oxc-project/runtime': 0.114.0
lightningcss: 1.31.1
@@ -2792,7 +2810,7 @@ snapshots:
rolldown: 1.0.0-rc.5
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.3.2
'@types/node': 25.3.3
esbuild: 0.27.3
fsevents: 2.3.3
jiti: 2.6.1

View File

@@ -1,6 +1,8 @@
import { Query, DataOpts } from '@kevisual/query';
import { QueryLoginBrowser } from '@kevisual/api/query-login'
import { useContextKey } from '@kevisual/context';
import { QueryClient } from '@tanstack/react-query';
export const query = useContextKey('query', new Query({
url: '/api/router',
}));
@@ -11,4 +13,6 @@ export const queryClient = useContextKey('queryClient', new Query({
export const queryLogin = useContextKey('queryLogin', new QueryLoginBrowser({
query: query
}));
}));
export const stackQueryClient = useContextKey('stackQueryClient', new QueryClient());

View File

@@ -6,6 +6,8 @@ import { AuthProvider } from '@/pages/auth'
import { TooltipProvider } from '@/components/ui/tooltip'
import { useLayoutStore } from '@/pages/auth/store';
import { useShallow } from 'zustand/shallow';
import { stackQueryClient } from '@/modules/query'
import { QueryClientProvider } from '@tanstack/react-query'
import clsx from 'clsx';
export const Route = createRootRoute({
component: RootComponent,
@@ -17,20 +19,22 @@ function RootComponent() {
showBaseHeader: state.showBaseHeader,
})));
return (
<div className='h-full overflow-hidden'>
<LayoutMain />
<AuthProvider mustLogin={true}>
<TooltipProvider>
<main className={clsx('overflow-auto scrollbar', {
'h-[calc(100%-3rem)]': store.showBaseHeader,
'h-full': !store.showBaseHeader,
})}>
<Outlet />
</main>
</TooltipProvider>
</AuthProvider>
<TanStackRouterDevtools position="bottom-right" />
<Toaster />
</div>
<QueryClientProvider client={stackQueryClient}>
<div className='h-full overflow-hidden'>
<LayoutMain />
<AuthProvider mustLogin={true}>
<TooltipProvider>
<main className={clsx('overflow-auto scrollbar', {
'h-[calc(100%-3rem)]': store.showBaseHeader,
'h-full': !store.showBaseHeader,
})}>
<Outlet />
</main>
</TooltipProvider>
</AuthProvider>
<TanStackRouterDevtools position="bottom-right" />
<Toaster />
</div>
</QueryClientProvider>
)
}