likes
comments
collection
share

React Router V6 手摸手随便指南指北React Router V6 手摸手随便指南指北 目录 简介 为什么

作者站长头像
站长
· 阅读数 22

React Router V6 手摸手随便指南指北

React Router V6 手摸手随便指南指北React Router V6 手摸手随便指南指北 目录 简介 为什么

目录

  1. 简介
  2. 为什么升级到React Router V6
  3. React Router V6的主要特性
  4. 高级特性
  5. React Router V6原理解析
  6. 从V5迁移到V6
  7. 最佳实践
  8. 常见问题与解答
  9. 结论

简介(≈废话)


React Router是React生态系统中最流行的路由库之一。随着V6版本的发布,它带来了许多新特性和改进,使得路由管理变得更加简单和强大。本教程将全面介绍React Router V6的使用方法、新特性以及背后的原理。

ps:其实本来是不想写的,由于单位的小伙子执着于新特性的探索,写一篇能够讲清楚的文章。个人认为当你学习一样新东西的时候你只是动手写了这还不够,如果你能够写去尝试然后将你的这个过程文档化,我相信你会有更多的收获,

为什么升级到React Router V6

升级到React Router V6有以下几个主要原因 (我个人建议是不升,新项目可以尝试):

  1. 更简单的API:V6简化了许多API,使得代码更加简洁。
  2. 更强大的路由嵌套:V6提供了更直观的路由嵌套方式。
  3. 自动化的路由配置:不再需要手动设置Switch和exact属性。
  4. 更好的TypeScript支持:增强了对TypeScript的类型推断。
  5. 一致的行为:统一了路由和链接的行为,避免了V5中的一些不一致性。
  6. 性能优化:V6在内部进行了多项优化,提高了路由的性能。

React Router V6的主要特性

1. 基本使用

首先,让我们看一个基本的路由配置:

/ 匹配
/about 匹配
/contact 匹配
不匹配
URL请求
路由匹配
Home组件
About组件
Contact组件
404组件
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <Router>
      {/* 使用 BrowserRouter 来创建路由器 */}
      <Routes>
        {/* 定义路由规则 */}
        <Route path="/" element={<Home />} /> {/* 匹配根路径('/') */}
        <Route path="/about" element={<About />} /> {/* 匹配路径'/about' */}
        <Route path="/contact" element={<Contact />} /> {/* 匹配路径'/contact' */}
        <Route path="/contact" element={<Error404 />} /> {/* 匹配路径'/error404' */}

      </Routes>
    </Router>
  );
}

function Home() {
  return <h2>Home Page</h2>;
}

function About() {
  return <h2>About Page</h2>;
}

function Contact() {
  return <h2>Contact Page</h2>;
}

export default App;

2. 动态路由

V6中动态路由的使用更加直观:

URLRouterRouteUserComponentuseParamsView/user/123匹配 "/user/:id"渲染获取参数{id: "123"}渲染用户信息URLRouterRouteUserComponentuseParamsView
<Route path="/user/:id" element={<User />} />

// 在User组件中
import { useParams } from 'react-router-dom';

function User() {
  let { id } = useParams();
  return <h2>User ID: {id}</h2>;
}

3. 路由嵌套

V6强化了嵌套路由的能力:

App
Layout
Header
Content
Routes
Home
About
Contact
Footer
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="contact" element={<Contact />} />
        </Route>
      </Routes>
    </Router>
  );
}

function Layout() {
  return (
    <div>
      <nav>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/about">About</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>
      <Outlet />
    </div>
  );
}

4. 重定向与导航

使用Navigate组件处理重定向:

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

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/old-path" element={<Navigate to="/new-path" />} />
        <Route path="/new-path" element={<NewPage />} />
      </Routes>
    </Router>
  );
}

5. 使用useNavigate进行编程式导航

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

function Home() {
  // 定义一个导航函数,用于跳转页面
  let navigate = useNavigate();

  // 点击事件处理函数
  function handleClick() {
    // 使用导航函数跳转到 "/about" 路径
    navigate('/about');
  }

  return (
    <button onClick={handleClick}>跳转到关于页面</button>
  );
}

6. 使用 useSearchParams 处理查询参数

React Router V6 引入了 useSearchParams 钩子,它提供了一种便捷的方式来读取和修改 URL 的查询字符串。这个钩子返回一个包含两个元素的数组:当前位置的搜索参数和一个用于更新它们的函数。

URL: /search?q=react&page=1
useSearchParams
searchParams
q=react
page=1
setSearchParams
更新URL

基本用法如下:

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

function SearchComponent() {
  // 解构 useSearchParams hook,得到搜索参数对象和更新函数
  const [searchParams, setSearchParams] = useSearchParams();

  function handleSubmit(event) {
    event.preventDefault(); // 阻止表单默认提交行为

    // 获取表单输入框的值
    const query = event.target.query.value;

    // 更新搜索参数,设置 q 参数为输入值
    setSearchParams({ q: query });
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input name="query" placeholder="Search..." />
        <button type="submit">Search</button>
      </form>
      <p>
        The current query is: {searchParams.get('q') || 'none'} {/* 显示当前查询参数,如果没有则显示 'none' */}
      </p>
    </div>
  );
}

export default SearchComponent; // 可选:导出 SearchComponent 组件

在这个例子中:

  1. 我们使用 useSearchParams() 获取当前的搜索参数和更新它们的函数。
  2. searchParams.get('q') 用于获取名为 'q' 的查询参数的值。
  3. setSearchParams({ q: query }) 用于更新查询参数。这会导致 URL 更新,但不会引起页面刷新。

高级用法

useSearchParams 还支持更复杂的操作:

  1. 多个参数

    setSearchParams({ q: query, page: '1' });
    
  2. 删除参数

    searchParams.delete('page');
    setSearchParams(searchParams);
    
  3. 检查参数是否存在

    if (searchParams.has('q')) {
      // 执行搜索
    }
    
  4. 获取所有参数

    for (let [key, value] of searchParams.entries()) {
      console.log(key, value);
    }
    

注意事项

  1. useSearchParams 返回的 searchParams 对象是不可变的。要修改查询参数,必须使用 setSearchParams

  2. 当使用 setSearchParams 时,它会替换所有现有的查询参数。如果你只想更新部分参数,需要先获取当前参数,然后合并新的参数:

    setSearchParams(prevParams => {
      prevParams.set('page', '2');
      return prevParams;
    });
    
  3. useSearchParams 和 React 的状态管理是独立的。更新查询参数不会直接触发组件重新渲染,除非你的组件正在读取这些参数。

与服务端渲染的兼容性

如果你的应用使用服务端渲染,你可能需要使用 useSearchParams 的一个变体:

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

function SearchComponent() {
  let [searchParams, setSearchParams] = useSearchParams();

  // 在服务端渲染时,提供一个默认值
  searchParams = searchParams || new URLSearchParams();

  // 其余代码保持不变
}

这确保了即使在服务端(没有实际的 URL)也能正常工作。

通过使用 useSearchParams,你可以轻松地管理 URL 查询参数,实现更复杂的路由逻辑和状态管理,特别适用于需要保存和共享搜索、过滤或分页状态的场景。这进一步增强了 React Router V6 的功能,使得构建功能丰富的单页应用变得更加简单。

高级特性

路由懒加载

React Router V6结合React的Suspenselazy可以轻松实现路由懒加载:

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

const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

自定义钩子

往后站一站我要开始装X了

自定义钩子是 React 中复用逻辑的强大方式。在 React Router 中,我们可以创建自定义钩子来封装和复用常用的路由逻辑,使得代码更加清晰和易于维护。以下是一个扩展的 useRouter 自定义钩子示例:

import { 
  useLocation, 
  useNavigate, 
  useParams,
  useSearchParams
} from 'react-router-dom';

function useRouter() {
  // 获取当前位置对象
  const location = useLocation();
  
  // 获取用于编程式导航的函数
  const navigate = useNavigate();
  
  // 获取 URL 参数
  const params = useParams();
  
  // 获取查询参数
  const [searchParams, setSearchParams] = useSearchParams();

  return {
    // 当前路径
    pathname: location.pathname,
    // 查询字符串
    search: location.search,
    // 完整的 URL 路径
    fullPath: location.pathname + location.search,
    // 哈希值
    hash: location.hash,
    // 导航函数
    navigate,
    // 路由参数
    params,
    // 获取指定查询参数
    getQuery: (key) => searchParams.get(key),
    // 设置查询参数
    setQuery: (key, value) => {
      const newSearchParams = new URLSearchParams(searchParams);
      newSearchParams.set(key, value);
      setSearchParams(newSearchParams);
    },
    // 移除查询参数
    removeQuery: (key) => {
      const newSearchParams = new URLSearchParams(searchParams);
      newSearchParams.delete(key);
      setSearchParams(newSearchParams);
    }
  };
}

这个扩展的 useRouter 钩子提供了更多功能:

  1. pathname:获取当前路径。
  2. search:获取查询字符串。
  3. fullPath:获取完整的 URL 路径(包括查询字符串)。
  4. hash:获取 URL 的哈希值。
  5. navigate:用于编程式导航的函数。
  6. params:获取路由参数。
  7. getQuery:获取指定的查询参数值。
  8. setQuery:设置查询参数。
  9. removeQuery:移除指定的查询参数。

使用这个自定义钩子的例子:

function SomeComponent() {
  const router = useRouter();

  useEffect(() => {
    // 获取当前路径
    console.log('Current path:', router.pathname);

    // 获取路由参数
    console.log('Route params:', router.params);

    // 获取查询参数
    console.log('Query param "id":', router.getQuery('id'));
  }, []);

  const handleNavigate = () => {
    // 使用导航函数
    router.navigate('/new-path');
  };

  const handleSetQuery = () => {
    // 设置查询参数
    router.setQuery('filter', 'active');
  };

  return (
    <div>
      <button onClick={handleNavigate}>Navigate</button>
      <button onClick={handleSetQuery}>Set Query</button>
    </div>
  );
}

通过使用这个自定义钩子,我们可以在组件中更方便地访问和操作路由相关的信息和功能。这不仅使代码更加简洁,还提高了可重用性和可维护性。在大型应用中,这种封装可以显著减少重复代码,并为团队提供一致的路由操作接口。

路由守卫

实现路由守卫可以使用自定义组件:

访问受保护路由
是否已登录?
允许访问
重定向到登录页
登录
登录成功?
重定向到原始请求页面
留在登录页
function PrivateRoute({ children }) {
  const auth = useAuth();
  const navigate = useNavigate();

  if (!auth.user) {
    // 重定向到登录页
    return <Navigate to="/login" />;
  }

  return children;
}

// 使用
<Route
  path="/dashboard"
  element={
    <PrivateRoute>
      <Dashboard />
    </PrivateRoute>
  }
/>

React Router V6原理

  1. 路由匹配算法:V6使用递归匹配算法,根据路由配置自动匹配最符合的路径。这个算法的工作方式如下: a. 从最顶层的路由开始,将当前URL路径与每个路由的路径模式进行比较。

    b. 如果找到匹配,则继续检查该路由的子路由。

    c. 这个过程递归进行,直到找到最深层的匹配路由或遍历完所有可能的路由。 d. 算法会选择"最具体"的匹配,即路径最长的匹配。 例如,对于URL "/users/123/profile",路由 "/users/:id/profile" 会比 "/users/:id" 更优先匹配。

  2. 基于上下文的数据传递:使用React Context API来在组件树中传递路由信息。这允许任何深度的子组件都能访问到当前的路由状态,而不需要通过props层层传递。

  3. 组件化的设计:每个路由功能都被封装成独立的组件或钩子,方便组合和复用。例如,<Route><Link>useParams()等都是独立的单元,可以灵活组合使用。

  4. 路由配置的扁平化:V6简化了路由配置,不再需要严格的嵌套结构。这使得路由配置更加灵活,也更容易理解和维护。

从V5迁移到V6

从V5迁移到V6的主要步骤包括:

  1. 替换SwitchRoutes
  2. 移除exact属性,V6默认精确匹配。
  3. component属性改为element
  4. 使用useNavigate替代useHistory
  5. 调整嵌套路由的结构,使用Outlet

示例

替换SwitchRoutes
// V5
<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
</Switch>

// V6
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
</Routes>
// V5
<Route exact path="/users" component={Users} />

// V6
<Route path="/users" element={<Users />} />
component属性改为element
// V5
<Route path="/contact" component={Contact} />

// V6
<Route path="/contact" element={<Contact />} />
使用useNavigate替代useHistory
// V5
import { useHistory } from 'react-router-dom';
const history = useHistory();
history.push('/home');

// V6
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/home');
调整嵌套路由的结构,使用Outlet

<Outlet> 是一个特殊的占位符组件,用于在嵌套路由中渲染匹配到的子路由组件。它就像一个“空位”,等待着被子路由的内容填充。

主要作用:

  • 实现嵌套路由: 将子路由组件渲染到指定位置。
  • 创建共享布局: 在不同页面中共享相同的布局。
  • 动态渲染内容: 根据 URL 动态渲染不同组件。

工作原理:

React Router 根据 URL 匹配路由,并将匹配到的子路由组件渲染到 <Outlet> 的位置。

总结:

<Outlet> 是构建嵌套路由的核心组件,它让你的应用结构更清晰,维护更方便。

// V5
<Route path="/users">
  <Route path="/" component={UserList} />
  <Route path="/:id" component={UserDetail} />
</Route>

// V6
<Route path="/users" element={<UserLayout />}>
  <Route index element={<UserList />} />
  <Route path=":id" element={<UserDetail />} />
</Route>

// UserLayout.js
import { Outlet } from 'react-router-dom';
function UserLayout() {
  return (
    <div>
      <h1>Users</h1>
      <Outlet />
    </div>
  );
}
  • 解释:

    • Outlet 组件是一个占位符,用于渲染匹配到的子路由组件。
    • UserLayout 组件中,<Outlet /> 会被替换成对应的子路由组件(UserListUserDetail)。
两者对比
  • V5 使用 component 属性指定组件,而 V6 使用 element 属性。
  • V5 没有明确的布局组件概念,V6 引入了 Outlet 组件来处理布局。
  • V6 的写法更简洁,语义更清晰。

最佳实践

  1. 使用声明式路由配置,避免硬编码路径。
  2. 充分利用路由嵌套来组织复杂的应用结构。
  3. 使用懒加载优化大型应用的性能。
  4. 创建自定义钩子来封装常用的路由逻辑。
  5. 使用TypeScript来增强类型安全。

常见问题与解答

  1. Q: 如何处理404页面? A: 使用*路径匹配所有未定义的路由:

    <Route path="*" element={<NotFound />} />
    
  2. Q: 如何实现路由过渡动画? A: 可以结合React Transition Group库实现:

    import { CSSTransition, TransitionGroup } from 'react-transition-group';
    
    function App() {
      const location = useLocation();
      return (
        <TransitionGroup>
          <CSSTransition key={location.pathname} classNames="fade" timeout={300}>
            <Routes location={location}>
              {/* 路由配置 */}
            </Routes>
          </CSSTransition>
        </TransitionGroup>
      );
    }
    
  3. Q: 如何在路由变化时滚动到页面顶部? A: 可以创建一个自定义组件:

    function ScrollToTop() {
      const { pathname } = useLocation();
      
      useEffect(() => {
        window.scrollTo(0, 0);
      }, [pathname]);
      
      return null;
    }
    
    // 在Router内使用
    <Router>
      <ScrollToTop />
      {/* 其他组件 */}
    </Router>
    
    
    
  4. Q: 如何处理认证路由? A: 可以创建一个高阶组件来处理认证逻辑:

    function RequireAuth({ children }) {
      let auth = useAuth();
      let location = useLocation();
    
      if (!auth.user) {
        return <Navigate to="/login" state={{ from: location }} replace />;
      }
    
      return children;
    }
    
    // 使用
    <Route
      path="/protected"
      element={
        <RequireAuth>
          <ProtectedComponent />
        </RequireAuth>
      }
    />
    
  5. Q: 如何实现动态路由? A: 可以使用路由参数和 useParams 钩子:

    <Route path="/users/:userId" element={<UserProfile />} />
    
    function UserProfile() {
      let { userId } = useParams();
      // 使用 userId 获取用户数据
      return <div>User Profile for user {userId}</div>;
    }
    
  6. Q: 如何在路由组件外访问路由信息? A: 可以使用 useNavigateuseLocation 等钩子,但需要确保组件在 Router 上下文中:

    import { useNavigate } from 'react-router-dom';
    
    function SomeComponent() {
      let navigate = useNavigate();
    
      function handleClick() {
        navigate('/some-path');
      }
    
      return <button onClick={handleClick}>Go to Some Path</button>;
    }
    

结论

React Router V6通过简化API、增强路由嵌套能力、改进TypeScript支持等特性,为开发者提供了更好的路由管理解决方案。它不仅使得代码更加简洁,也提高了应用的性能和可维护性。无论是新项目还是正在考虑升级的既有项目,React Router V6都是一个值得采用的选择。

看到这里,相信你应该已经掌握了React Router V6的基本使用方法、高级特性以及一些最佳实践。随着你在实际项目中的应用,相信你会发现更多React Router V6带来的便利之处。(非必要不要去动老项目)

转载自:https://juejin.cn/post/7403910440608907327
评论
请登录