React动态路由配置踩坑记:从基础到进阶的实战经验总结

司马春依 前端 阅读 961
赞 28 收藏
二维码
手机扫码查看
反馈

动态路由的基本玩法

最近项目里用了不少动态路由,踩了几个坑,记录一下。动态路由其实就是在URL里面塞个变量,比如用户ID,商品ID这种,React Router和Vue Router都有对应的实现。

React动态路由配置踩坑记:从基础到进阶的实战经验总结

先说React Router的写法,这是我在项目中最常用的:

import { BrowserRouter as Router, Routes, Route, useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  
  return (
    <div>
      <h1>用户ID: {userId}</h1>
    </div>
  );
}

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/user/:userId" element={<UserProfile />} />
        <Route path="/product/:productId" element={<ProductDetail />} />
      </Routes>
    </Router>
  );
}

这里的关键就是那个冒号 ::userId 就表示这是一个动态参数。然后在组件里用 useParams() 钩子来获取参数值。亲测有效,基本不会出问题。

Vue Router的动态路由实现

Vue这边也差不多,不过语法稍微不同:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import UserDetail from '../components/UserDetail.vue'

const routes = [
  { path: '/', component: Home },
  { 
    path: '/user/:userId', 
    component: UserDetail,
    props: true // 这个很重要,把路由参数作为props传给组件
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router
<!-- UserDetail.vue -->
<template>
  <div>
    <h1>用户ID: {{ userId }}</h1>
  </div>
</template>

<script>
export default {
  props: ['userId'],
  mounted() {
    console.log(this.userId) // 这里就能拿到路由参数了
  }
}
</script>

Vue这边需要注意 props: true 这个配置,不然参数传不到组件里去。之前我就因为忘了加这个,调试了半小时。

嵌套路由的动态参数

复杂一点的场景是嵌套路由,比如 /user/:userId/orders/:orderId 这种,参数嵌套参数:

<Route path="/user/:userId">
  <Route path="orders/:orderId" element={<OrderDetail />} />
</Route>

这样在OrderDetail组件里就可以同时拿到userId和orderId:

function OrderDetail() {
  const { userId, orderId } = useParams();
  
  useEffect(() => {
    // 可以用这两个参数请求具体的数据
    fetchUserOrder(userId, orderId);
  }, [userId, orderId]);
  
  return <div>订单详情</div>;
}

嵌套路由的参数获取非常直观,所有参数都会合并到一个对象里返回。

参数验证和错误处理

实际项目中参数校验是必须的,不能假设用户输入的URL都是合法的。我在项目里加了一层封装:

function ValidatedUserProfile() {
  const { userId } = useParams();
  
  // 参数校验
  if (!userId || isNaN(userId)) {
    return <div>无效的用户ID</div>;
  }
  
  // 转换数据类型
  const numericUserId = parseInt(userId);
  
  return <UserProfile userId={numericUserId} />;
}

这里我踩过坑,用户可能输入字母或者特殊字符,如果不验证就会导致后端接口报错。建议在useEffect里也加个条件判断:

useEffect(() => {
  if (userId && !isNaN(userId)) {
    fetchUserData(userId);
  }
}, [userId]);

可选参数和通配符路由

有时候需要可选参数,比如 /search?query=xxx&page=1 这种,可以用问号:

<Route path="/search/:query?" element={<SearchResults />} />

这里query是可选的,没有的话params.query就是undefined。还有通配符路由,用来捕获剩余路径:

<Route path="/docs/*" element={<DocsPage />} />

通配符比较有用的是做404页面:

<Route path="*" element={<NotFound />} />

这个必须放在所有路由的最后面,不然会拦截其他路由。

编程式导航传参

除了URL参数,有时候还需要传递额外数据。React Router里用state:

import { useNavigate } from 'react-router-dom';

function MyComponent() {
  const navigate = useNavigate();
  
  const handleGoToUser = (userId, userData) => {
    navigate(/user/${userId}, {
      state: { userData } // 额外数据
    });
  };
  
  return <button onClick={() => handleGoToUser(123, {name: '张三'})}>去用户页</button>;
}
function UserProfile() {
  const location = useLocation();
  const { userId } = useParams();
  const { userData } = location.state || {};
  
  return (
    <div>
      <h1>{userData?.name || '加载中...'}</h1>
    </div>
  );
}

Vue那边类似,不过用query参数更常见:

const router = useRouter();

router.push({
  path: '/user',
  query: { 
    id: 123,
    from: 'dashboard'
  }
});

踩坑提醒:这几点一定注意

动态路由最容易踩坑的地方有几个:

  • 参数类型转换:URL里的参数都是字符串,数字ID记得转成number类型
  • 路由顺序:静态路由在前,动态路由在后,不然会被静态路由拦截
  • 参数变化监听:在同一个组件内切换参数时,组件不会重新mount,需要用useEffect监听参数变化
  • 编码问题:特殊字符记得encodeURIComponent,否则可能路由匹配失败

特别是第三点,我在项目里遇到过bug,用户从 /user/1 切到 /user/2,页面没刷新数据。后来加上useEffect监听解决了:

useEffect(() => {
  // 参数变化时重新请求数据
  refreshData(userId);
}, [userId]);

性能优化小技巧

动态路由的数据加载策略也很重要。我一般会先显示骨架屏,避免用户看到空白页面:

function UserProfile({ userId }) {
  const [loading, setLoading] = useState(true);
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    setLoading(true);
    fetchUserData(userId).then(data => {
      setUserData(data);
      setLoading(false);
    });
  }, [userId]);
  
  if (loading) {
    return <SkeletonLoader />;
  }
  
  return <UserContent data={userData} />;
}

还可以考虑预加载策略,用户鼠标悬停链接时提前加载数据,提升用户体验。

以上是我对动态路由的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论