继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

蛊毒传说
关注TA
已关注
手记 245
粉丝 18
获赞 84

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

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP