为了账号安全,请及时绑定邮箱和手机立即绑定

如何用TanStack套件打造现代React应用(附2025年最新教程)

TanStack 工具套件是一个既引人注目又现代的技术栈,它让开发者能够构建功能强大的全栈应用程序。该套件由 Vinxi 驱动,Vinxi 是一个 JavaScript SDK,可以使用 Vite 构建全栈应用,以便可以部署到任何 JavaScript 可运行的地方。该套件为前端开发者提供了一流的开发体验,同时集成了丰富的后端专业知识,因此,你将获得前后端的最佳结合。

在2025年,TanStack Start可能会与Astro、Next.js和Remix一起非常流行,用于构建React应用程序。今天,我们将了解TanStack套件中最常用的工具的基础,并看看它们如何帮助构建2025年的现代React应用程序。

我们将要探讨的工具有,包括:

搭建 TanStack Start 和 TanStack Router 项目

好的,现在让我们开始设置我们的 React 项目,我们从 TanStack Start 开始。首先,在你的电脑上找到一个目录,比如桌面,然后使用这个脚本来设置项目:

    mkdir tanstack-project
    cd tanstack-project
    npm create @tanstack/router@latest --legacy-peer-deps

全屏模式 退出全屏

使用此脚本,我们会创建一个名为 tanstack-project 的项目文件夹,并安装所需的软件包。

截至目前为止,TanStack Router 需要 React v18.3.1,所以如果你安装了更新版本,控制台可能会报错。最简单的解决办法是使用 --legacy-peer-deps 标志,这可以忽略由互相依赖的包引起的冲突。对于简单的测试来说这样做是可以的;而在实际使用中,建议使用更好的解决方法。

参考项目设置指南,我用的是下面这种配置:

  • 项目名称: my-router-app
  • 打包工具: Vite
  • IDE: cursor

要运行你的应用程序,请确保你在项目文件夹里,然后运行下面的命令。

运行 npm run dev 来启动开发模式

全屏/退出全屏

你现在应该已经看到默认的 TanStack Router 主页,该主页带有首页和关于页面的路由。

https://imgapi.imooc.com/678df764093f5edd08000446.jpg 案例图片:TanStack 官方首页

使用 TanStack Query 来管理状态

我们现在有了应用程序的全局状态管理已经到位,接下来需要启动并运行TanStack Query。我们先安装所需的依赖项,运行下面的命令:

运行命令 npm install @tanstack/react-query @tanstack/react-query-devtools 来安装这两个包。

点击这里进入全屏,点击这里退出全屏

通过这些命令,我们现在能够访问我们应用程序的全局状态。

接下来,我们使用免费的JSONPlaceHolder API做一个简单的博客项目。好消息是,我们只需要修改两个文件就可以让这个项目跑起来,其中第一个步骤是将src/main.tsx文件里的所有代码替换成下面的新代码。

import React from 'react' // 导入React库
import ReactDOM from 'react-dom/client' // 导入React的DOM渲染模块
import { RouterProvider, createRouter } from '@tanstack/react-router' // 导入路由提供者和创建路由的函数
import { routeTree } from './routeTree.gen' // 导入路由树
import { 
  QueryClient, 
  QueryClientProvider 
} from '@tanstack/react-query' // 导入查询客户端和查询客户端提供者
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' // 导入React查询工具调试组件

// 创建一个新的查询客户端,并设置默认选项
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 缓存数据过期时间
      gcTime: 1000 * 60 * 60, // 清理缓存的时间
    },
  },
})

// 创建一个新的路由器实例
const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
})

// 声明模块,用于自定义路由
declare module '@tanstack/react-router' {
  interface Register { // 注册接口
    router: typeof router // 路由器对象
  }
}

// 获取ID为'app'的DOM元素
const rootElement = document.getElementById('app')!

if (!rootElement.innerHTML) { // 如果rootElement内容为空
  const root = ReactDOM.createRoot(rootElement) // 创建一个新的根实例
  root.render( // 渲染根实例
    <React.StrictMode>
      <QueryClientProvider client={queryClient}> // 提供查询客户端
        <RouterProvider router={router} /> // 提供路由
        <ReactQueryDevtools initialIsOpen={false} /> // 初始化调试工具,初始状态为关闭
      </QueryClientProvider>
    </React.StrictMode>
  )
}

点击全屏进入/退出

这段代码基本上配置了我们的 React Query 客户端,使其在整个应用程序中都可以使用。它还具有某些默认查询参数,使其功能更佳。

最后,让我们也这样做一下,替换和更新文件内的所有代码。

    import * as React from 'react'
    import { createFileRoute } from '@tanstack/react-router'
    import { useQuery } from '@tanstack/react-query'

    interface Post {
      id: number;
      title: string;
      body: string;
    }

    const fetchPosts = async (): Promise<Post[]> => {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts')
      if (!response.ok) {
        throw new Error('网络响应出错')
      }
      return response.json()
    }

    export const Route = createFileRoute('/')({
      component: HomeComponent,
    })

    function HomeComponent() {
      const { 
        data: posts, 
        isLoading, 
        isError, 
        error 
      } = useQuery<Post[], Error>({
        queryKey: ['posts'],
        queryFn: fetchPosts,
      })

      if (isLoading) {
        return <div>加载中...</div>
      }

      if (isError) {
        return <div>错误: {error.message}</div>
      }

      return (
        <div className="p-4">
          <h3 className="text-2xl font-bold mb-4">欢迎来到首页!</h3>
          <h4 className="text-xl mb-2">最新帖子:</h4>
          <div className="space-y-4">
            {posts?.slice(0, 5).map((post) => (
              <div key={post.id} className="bg-gray-600 p-3 rounded">
                <h5 className="font-semibold">{post.title}</h5>
                <p>{post.body}</p>
              </div>
            ))}
          </div>
        </div>
      )
    }

切换到全屏,退出全屏

这个文件创建了一个 fetchPosts 函数来从 JSONPlaceholder API 获取帖子内容,并且使用了 useQuery 钩子来获取数据、管理状态和显示数据。使用 Tailwind CSS 进行样式设置。

你现在应该有一个类似下面这个例子的设计。

演示了TanStack首页的数据图表

创建一个 TanStack Table 来展示数据.

在我们开始之前,需要安装@tanstack/react-table,请使用下面的脚本来安装。

运行以下命令来安装@tanstack/react-table插件:

    npm install @tanstack/react-table

全屏(进入/退出)

我们的项目已经设置好了使用 TanStack 表格,我们现在可以开始处理这些文件了。我们需要创建一个名为 components 的文件夹,然后在这个文件夹内创建一个 DataTable.tsx 文件。

在下面的文件里加上这段代码:
将此代码添加到 components/DataTable.tsx 文件中:

    import React, { useState } from 'react'
    import {
      ColumnDef,
      flexRender,
      getCoreRowModel,
      useReactTable,
    } from '@tanstack/react-table'

    type Person = {
      firstName: string
      lastName: string
      age: number
      visits: number
      status: string
    }

    const defaultData: Person[] = [
      {
        firstName: 'John',
        lastName: 'Doe',
        age: 30,
        visits: 5,
        status: 'Active',
      },
      {
        firstName: 'Jane',
        lastName: 'Smith',
        age: 25,
        visits: 3,
        status: 'Inactive',
      },
    ]

    const defaultColumns: ColumnDef<Person>[] = [
      {
        accessorKey: 'firstName',
        header: 'First Name',
        cell: (info) => info.getValue(),
      },
      {
        accessorKey: 'lastName',
        header: 'Last Name',
        cell: (info) => info.getValue(),
      },
      {
        accessorKey: 'age',
        header: 'Age',
        cell: (info) => info.getValue(),
      },
      {
        accessorKey: 'visits',
        header: 'Visits',
        cell: (info) => info.getValue(),
      },
      {
        accessorKey: 'status',
        header: 'Status',
        cell: (info) => info.getValue(),
      },
    ]

    export function DataTable() {
      const [data] = useState(() => [...defaultData])
      const [columns] = useState<ColumnDef<Person>[]>(() => [...defaultColumns])

      const table = useReactTable({
        data,
        columns,
        getCoreRowModel: getCoreRowModel(),
      })

      return (
        <div className="p-2">
          <table className="w-full border-collapse border border-gray-300">
            <thead>
              {table.getHeaderGroups().map((headerGroup) => (
                <tr key={headerGroup.id}>
                  {headerGroup.headers.map((header) => (
                    <th 
                      key={header.id} 
                      className="border border-gray-300 p-2 bg-gray-600"
                    >
                      {header.isPlaceholder
                        ? null
                        : flexRender(
                            header.column.columnDef.header,
                            header.getContext()
                          )}
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody>
              {table.getRowModel().rows.map((row) => (
                <tr key={row.id} className="hover:bg-gray-800">
                  {row.getVisibleCells().map((cell) => (
                    <td 
                      key={cell.id} 
                      className="border border-gray-300 p-2"
                    >
                      {flexRender(
                        cell.column.columnDef.cell,
                        cell.getContext()
                      )}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )
    }

全屏显示 退出全屏

在这份文件中,我们创建了一个用户对象的数组,然后将其输出至使用 TanStack 库创建的新表中。

最后修改 routes/about.tsx 文件如下所示:

    import * as React from 'react'
    import { createFileRoute } from '@tanstack/react-router'
    import { DataTable } from '../components/DataTable'

    export const Route = createFileRoute('/about')({
      component: AboutComponent,
    })

    function AboutComponent() {
      return (
        <div className="p-2">
          <h3>用户列表</h3>
          <DataTable />
        </div>
      )
    }

进入全屏 退出全屏

这段代码更新了关于我们页面的用户新标题,并将新的数据表添加到页面上。您的关于我们页面现在应该有一个类似的表格,如下所示:

https://res.cloudinary.com/d74fh3kw/image/upload/v1736973497/tanstack-about-page_lrp8i6.png

为操作行为添加 TanStack Form

最后一步,让我们在关于我们页面上添加一个表单,以完成我们的应用。就像我们之前所做的那样,我们首先需要将所需的包添加到我们的应用中。

使用此脚本来安装它们

在终端中运行以下命令来安装所需的包:

npm install @tanstack/react-form zod

进入全屏,退出全屏

我们使用了 TanStack 表单和 Zod 进行表单验证。

好的,我们现在需要做的就是将这个新的代码添加到我们的 routes/about.tsx 文件中,这个新代码中有我们的表单,我们的应用就完成了。

    import * as React from 'react'
    import { createFileRoute } from '@tanstack/react-router'
    import { DataTable } from '../components/DataTable'
    import { useForm } from '@tanstack/react-form'
    import { z } from 'zod'

    const formSchema = z.object({
      firstName: z.string().min(2, '名字至少需要两个字符'),
      lastName: z.string().min(2, '姓氏至少需要两个字符'),
      age: z.coerce.number().min(0, '年龄必须是非负数'),
    })

    type FormValues = z.infer<typeof formSchema>

    export const Route = createFileRoute('/about')({
      component: AboutComponent,
    })

    function AboutComponent() {
      const [errors, setErrors] = React.useState<Record<string, string>>({})
      const [formState, setFormState] = React.useState<FormValues>({
        firstName: '',
        lastName: '',
        age: 0,
      })

      const form = useForm<FormValues>({
        defaultValues: formState,
        onSubmit: async ({ value }) => {
          try {
            const validatedData = formSchema.parse(value)
            console.log('表单已提交:', validatedData)
            setErrors({})
            setFormState(validatedData)
          } catch (err) {
            if (err instanceof z.ZodError) {
              const newErrors: Record<string, string> = {}
              err.errors.forEach((error) => {
                if (error.path[0]) {
                  newErrors[error.path[0] as string] = error.message
                }
              })
              setErrors(newErrors)
            }
          }
        },
      })

      const validateField = (field: keyof FormValues, value: string | number) => {
        try {
          formSchema.shape[field].parse(value)
          setErrors(prev => ({ ...prev, [field]: '' }))
        } catch (err) {
          if (err instanceof z.ZodError) {
            setErrors(prev => ({ ...prev, [field]: err.errors[0].message }))
          }
        }
      }

      return (
        <div className="p-2 max-w-md mx-auto">
          <h3 className="text-2xl mb-4">用户信息</h3>
          <DataTable />

          <h3 className="text-2xl mt-6 mb-4">用户注册表单</h3>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              e.stopPropagation()
              void form.handleSubmit()
            }}
            className="space-y-4"
          >
            <div>
              <label htmlFor="firstName" className="block mb-2">名</label>
              <input
                id="firstName"
                type="text"
                value={form.state.values.firstName}
                onChange={(e) => {
                  const value = e.target.value
                  form.setFieldValue('firstName', value)
                  validateField('firstName', value)
                }}
                className="w-full p-2 border rounded"
              />
              {errors.firstName && (
                <p className="text-red-500 text-sm mt-1">
                  {errors.firstName}
                </p>
              )}
            </div>

            <div>
              <label htmlFor="lastName" className="block mb-2">姓</label>
              <input
                id="lastName"
                type="text"
                value={form.state.values.lastName}
                onChange={(e) => {
                  const value = e.target.value
                  form.setFieldValue('lastName', value)
                  validateField('lastName', value)
                }}
                className="w-full p-2 border rounded"
              />
              {errors.lastName && (
                <p className="text-red-500 text-sm mt-1">
                  {errors.lastName}
                </p>
              )}
            </div>

            <div>
              <label htmlFor="age" className="block mb-2">年龄</label>
              <input
                id="age"
                type="number"
                value={form.state.values.age}
                onChange={(e) => {
                  const value = Number(e.target.value)
                  form.setFieldValue('age', value)
                  validateField('age', value)
                }}
                className="w-full p-2 border rounded"
              />
              {errors.age && (
                <p className="text-red-500 text-sm mt-1">
                  {errors.age}
                </p>
              )}
            </div>

            <button 
              type="submit" 
              className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
            >
              保存并提交
            </button>
          </form>

          <div className="mt-6 p-4 bg-gray-600 rounded">
            <h3 className="text-xl mb-2">当前表单信息</h3>
            <pre className="bg-slate-200 p-2 rounded text-black">
              {JSON.stringify(formState, null, 2)}
            </pre>
          </div>
        </div>
      )
    }

全屏模式 退出全屏

这段代码将在我们的关于页面上添加一个用户注册表,该表还具备验证功能。表单会将数据展示在页面上。请看下面的例子,您的关于页面应该看起来一样。

https://imgapi.imooc.com/678df76509154bec08000565.jpg 查看图片

最后说一下

TanStack 工具套件可用于构建非常复杂的应用程序。多个工具甚至可以集成并用于其他框架,例如 Astro、Next.js 和 Remix。结合使用时,它们可以为您的项目提供全方位解决方案。今天,我们介绍了路由、状态管理和表格及表单构建的基础知识。我强烈建议您阅读 TanStack 官方文档,因为我们只是浅尝辄止。还有很多可以探索和实现的内容,文档涵盖了所有内容。

TanStack 工具套件还包括 TanStack Virtual,该库用于创建可滚动元素,TanStack Ranger 用于构建多范围滑块组件,TanStack Store 提供更强大的状态管理功能,以及 TanStack Config,用于配置和发布 JavaScript 包。凭借这种多功能性,TanStack 工具套件可以轻松实现高性能和功能丰富的 React 应用程序开发,在 2025 年。

此处省略部分内容

跟紧科技、编程、效率和人工智能的最新动态

如果你喜欢这些文章,就来关注我,在我的社交媒体上(链接),我会分享与这些话题相关的各种内容 🔥

https://imgapi.imooc.com/6784b29b0937fa4800000000.jpg

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消