react 路由 dataApi

184 阅读2分钟

一、数据加载器(Loader)

1. 基础用法

作用:在路由组件渲染前加载数据
适用场景:页面初始化数据获取、权限验证

// 路由配置
{
  path: '/user/:userId',
  element: <UserProfile />,
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    if (!response.ok) throw new Error('用户不存在');
    return response.json();
  }
}

// 组件中使用
import { useLoaderData } from 'react-router-dom';

const UserProfile = () => {
  const userData = useLoaderData(); // 直接获取 loader 返回的数据
  return <div>{userData.name}</div>;
};

2. 进阶功能

权限验证(自动重定向):

{
  path: '/dashboard',
  element: <Dashboard />,
  loader: () => {
    if (!localStorage.getItem('token')) {
      return redirect('/login'); // 自动跳转登录页
    }
    return null;
  }
}

并行加载(多个路由的 loader 同时执行):

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    loader: layoutLoader, // 第一个 loader
    children: [
      {
        index: true,
        element: <Home />,
        loader: homeLoader // 第二个并行执行的 loader
      }
    ]
  }
]);

二、数据操作(Action)

1. 基础表单提交

作用:处理表单 POST/PUT/DELETE 请求

// 路由配置
{
  path: '/posts/create',
  element: <CreatePost />,
  action: async ({ request }) => {
    const formData = await request.formData();
    const newPost = {
      title: formData.get('title'),
      content: formData.get('content')
    };
    
    const response = await fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify(newPost)
    });
    
    if (!response.ok) return { error: '创建失败' };
    return redirect('/posts'); // 成功后跳转
  }
}

// 组件中使用
import { Form, useActionData } from 'react-router-dom';

const CreatePost = () => {
  const actionData = useActionData(); // 获取 action 返回的数据
  
  return (
    <Form method="post">
      <input name="title" />
      <textarea name="content" />
      <button type="submit">提交</button>
      {actionData?.error && <div className="error">{actionData.error}</div>}
    </Form>
  );
};

2. 文件上传处理

action: async ({ request }) => {
  const formData = await request.formData();
  const file = formData.get('avatar');
  
  const uploadResponse = await fetch('/api/upload', {
    method: 'POST',
    body: file
  });

  return uploadResponse.json();
}

三、错误处理

1. 全局错误边界

// 路由配置
{
  path: '/',
  element: <Root />,
  errorElement: <ErrorPage />, // 错误显示组件
  children: [
    {
      path: 'product/:id',
      element: <Product />,
      loader: productLoader // 这里抛出的错误会被 errorElement 捕获
    }
  ]
}

// ErrorPage.jsx
import { useRouteError } from 'react-router-dom';

const ErrorPage = () => {
  const error = useRouteError();
  return (
    <div>
      <h1>出错啦!</h1>
      <p>{error.message}</p>
    </div>
  );
};

2. 局部错误处理

{
  path: '/settings',
  element: <Settings />,
  loader: async () => {
    try {
      const data = await fetchSecureData();
      return data;
    } catch (error) {
      throw new Response('权限不足', { status: 403 }); // 自定义错误响应
    }
  },
  errorElement: <AuthError /> // 专门处理权限错误的组件
}

四、最佳实践

  1. 分离数据逻辑
    将 loader/action 逻辑单独放在 routes 目录:

    src/
      routes/
        post/
          loader.js
          action.js
    
  2. 类型安全(TypeScript):

    interface LoaderData {
      post: Post;
      comments: Comment[];
    }
    
    export const postLoader = async ({ params }) => {
      // ...加载逻辑
      return data as LoaderData;
    }
    
    const PostPage = () => {
      const data = useLoaderData() as LoaderData;
      // ...
    }
    
  3. 缓存优化

    // 使用 stale-while-revalidate 策略
    loader: async () => {
      const cached = sessionStorage.getItem('cachedData');
      if (cached) return JSON.parse(cached);
      
      const freshData = await fetchData();
      sessionStorage.setItem('cachedData', JSON.stringify(freshData));
      return freshData;
    }
    
  4. 加载状态指示

    import { useNavigation } from 'react-router-dom';
    
    const SubmitButton = () => {
      const navigation = useNavigation();
      return (
        <button disabled={navigation.state === 'submitting'}>
          {navigation.state === 'submitting' ? '提交中...' : '提交'}
        </button>
      );
    };
    

五、注意事项

  1. 不要滥用 loader
    简单状态管理应使用 useState 或 Context,只有路由级别的数据需求才使用 loader

  2. 避免副作用
    loader 应该只用于数据获取,不要在其中修改全局状态

  3. 生产环境错误处理
    建议使用 Sentry 等工具捕获 loader/action 中的错误

  4. 浏览器兼容性
    Data API 需要支持现代浏览器(IE 不支持)


通过结合这些功能,您可以构建出: ✅ 自动处理加载状态的应用
✅ 具备统一错误处理的系统
✅ 支持乐观更新的交互界面
✅ 具备路由级权限控制的解决方案

建议通过官方示例库实践练习:
github.com/remix-run/r…