前端工程Technical Deep Dive

React 生态工具链 —— 数据获取、状态管理、表格实战

发布时间2026/03/29
分类前端工程
预计阅读3 分钟
作者吴长龙
*

React 生态全景

01.内容

React 生态全景

经过多年发展,React 生态已非常成熟。本文介绍数据获取、状态管理、表格等领域的主流工具。

一、数据获取:React Query / SWR

#### 为什么需要数据获取库?

jsx snippetjsx
// 原生 fetch 的问题
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <Loading />;
  if (error) return <Error error={error} />;
  return <List users={users} />;
}

问题:手写太多样板代码,需要处理 loading、error、缓存、轮询、预加载...

#### React Query(TanStack Query)

jsx snippetjsx
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserList />
    </QueryClientProvider>
  );
}

function UserList() {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => res.json()),
    staleTime: 5 * 60 * 1000, // 5分钟内数据不过期
    retry: 3, // 失败重试3次
  });
  
  if (isLoading) return <Loading />;
  if (error) return <Error error={error} />;
  
  return (
    <div>
      <button onClick={() => refetch()}>刷新</button>
      <List users={data} />
    </div>
  );
}

核心特性

  • 自动缓存:相同 queryKey 自动复用缓存
  • 后台更新:staleTime 后自动重新获取
  • 去重请求:相同请求合并为一个
  • 乐观更新:更新后立即反映,稍后同步服务器
jsx snippetjsx
// 乐观更新示例
const mutation = useMutation({
  mutationFn: updateUser,
  onMutate: async (newUser) => {
    // 取消当前进行的请求
    await queryClient.cancelQueries(['users', newUser.id]);
    
    // 快照旧数据
    const oldData = queryClient.getQueryData(['users', newUser.id]);
    
    // 立即更新缓存
    queryClient.setQueryData(['users', newUser.id], newUser);
    
    // 返回上下文,供失败时回滚
    return { oldData };
  },
  onError: (err, newUser, context) => {
    // 回滚到旧数据
    queryClient.setQueryData(['users', newUser.id], context.oldData);
  },
  onSettled: (data, err, variables) => {
    // 重新获取确保一致
    queryClient.invalidateQueries(['users']);
  },
});

#### SWR

SWR 是 Vercel 出的轻量级方案,API 类似但更简洁:

jsx snippetjsx
import useSWR from 'swr';

function UserList() {
  const { data, error, isLoading, mutate } = useSWR('/api/users', fetcher);
  
  if (isLoading) return <Loading />;
  if (error) return <Error error={error} />;
  
  return (
    <div>
      <button onClick={() => mutate()}>刷新</button>
      <List users={data} />
    </div>
  );
}

SWR vs React Query 对比

特性SWRReact Query
体积~6KB~12KB
API简洁功能丰富
DevTools基础完善
适用简单场景复杂场景

二、表格:TanStack Table

#### 为什么需要表格库?

表格是企业级应用最复杂的组件之一,需要处理:

  • 排序、筛选、分页
  • 列固定、行展开
  • 虚拟滚动(大数据)
  • 行列合并
  • 编辑模式

TanStack Table(原 React Table)是 headless 方案,帮你处理逻辑,样式完全自定义。

jsx snippetjsx
import { useReactTable, getCoreRowModel, getSortedRowModel } from '@tanstack/react-table';

function DataTable({ data }) {
  const [sorting, setSorting] = useState([]);
  
  const table = useReactTable({
    data,
    columns: [
      { header: 'Name', accessorKey: 'name' },
      { header: 'Age', accessorKey: 'age' },
      { 
        header: 'Actions',
        cell: ({ row }) => (
          <button onClick={() => handleEdit(row.original)}>编辑</button>
        )
      }
    ],
    state: { sorting },
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
  });
  
  return (
    <table>
      <thead>
        {table.getHeaderGroups().map(headerGroup => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map(header => (
              <th 
                key={header.id}
                onClick={header.column.getToggleSortingHandler()}
              >
                {header.column.getIsSorted() === 'asc' ? '↑' : 
                 header.column.getIsSorted() === 'desc' ? '↓' : ''}
                {flexRender(header.column.columnDef.header, header.getContext())}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map(row => (
          <tr key={row.id}>
            {row.getVisibleCells().map(cell => (
              <td key={cell.id}>
                {flexRender(cell.column.columnDef.cell, cell.getContext())}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

核心特性

  • Headless:完全控制 UI
  • 功能丰富:排序、筛选、分页、展开...
  • 类型安全:TypeScript 支持完善
  • 性能好:支持虚拟滚动

三、表单:React Hook Form

jsx snippetjsx
import { useForm, Controller } from 'react-hook-form';

function LoginForm() {
  const { control, handleSubmit, formState: { errors } } = useForm({
    defaultValues: { email: '', password: '' }
  });
  
  const onSubmit = (data) => {
    console.log(data);
  };
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        name="email"
        control={control}
        rules={{ required: '邮箱必填', pattern: /^\S+@\S+$/ }}
        render={({ field }) => (
          <div>
            <input {...field} />
            {errors.email && <span>{errors.email.message}</span>}
          </div>
        )}
      />
      
      <Controller
        name="password"
        control={control}
        rules={{ required: '密码必填', minLength: 6 }}
        render={({ field }) => (
          <div>
            <input type="password" {...field} />
            {errors.password && <span>{errors.password.message}</span>}
          </div>
        )}
      />
      
      <button type="submit">登录</button>
    </form>
  );
}

优点

  • 受控/非受控模式灵活切换
  • 性能好:只重渲染变化的字段
  • 验证规则丰富
  • 体积小(~9KB)

四、可视化:Recharts / Visx

#### Recharts

jsx snippetjsx
import { LineChart, Line, XAxis, YAxis, Tooltip } from 'recharts';

function SalesChart({ data }) {
  return (
    <LineChart data={data}>
      <XAxis dataKey="month" />
      <YAxis />
      <Tooltip />
      <Line type="monotone" dataKey="sales" stroke="#8884d8" />
    </LineChart>
  );
}

#### Visx

D3-based 的 React 可视化库,更底层但更灵活:

jsx snippetjsx
import { Group } from '@visx/group';
import { LinePath } from '@visx/shape';

function CustomChart({ data }) {
  return (
    <svg width={500} height={300}>
      <Group>
        <LinePath
          data={data}
          x={d => xScale(d.date)}
          y={d => yScale(d.value)}
          stroke="#8884d8"
          strokeWidth={2}
        />
      </Group>
    </svg>
  );
}

五、拖拽:dnd-kit

jsx snippetjsx
import { DndContext, closestCenter } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';

function SortableList({ items, setItems }) {
  function handleDragEnd(event) {
    const { active, over } = event;
    if (active.id !== over.id) {
      const oldIndex = items.findIndex(i => i.id === active.id);
      const newIndex = items.findIndex(i => i.id === over.id);
      // 重新排序...
    }
  }
  
  return (
    <DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
      <SortableContext items={items} strategy={verticalListSortingStrategy}>
        {items.map(item => <SortableItem key={item.id} item={item} />)}
      </SortableContext>
    </DndContext>
  );
}

选型建议

场景推荐方案
数据获取React Query(复杂)或 SWR(简单)
表格TanStack Table
表单React Hook Form
可视化Recharts(简单)或 Visx(复杂)
拖拽dnd-kit
状态管理参见上篇文章

总结

React 生态的核心思想是组合

  • 每个库专注一件事
  • 通过组合构建复杂应用
  • 选择适合项目规模的工具

不要过度工程化:小项目用 fetch + useState,中等项目用 SWR + Zustand,大型项目用 React Query + 自定义状态管理。