2023-React Router v6简明教程-使用篇

前言

看了一圈 react router 文章,发现写 v6 的文章好像不多,也大多是 v6.4 之前的,比较碎片化。然后看了文档,感觉官方文档写的也不是很简单清晰。每次大版本迭代 API 变动还是蛮大的,学习成本不小。

所以基于自己的学习和理解,梳理一下 react router v6 快速上手教程,没有去讲解 Data API 和偏底层的 API。当然详细的还是要看 官方文档blog 以及 examples 去学习。

版本历史

v3 到 v4/v5 到 v6 之间的 API 变化都是很大的,先了解一下 react router 的进化史:

6.x

目前 react-router 最新的版本为 v6.16.0(2023年9月),下个几个重要版本的发布节点,最重要的是 v6.4.0:

  • 2021年11月发布的 v6.0.0 版本,基于 hooks 重构,体积更小。
  • 2022 年9月发布的 v6.4.0 版本,引入 createBrowserRouter/createHashRouter<RouterProvider> 等Data API
  • 2023年 3月发布的 v6.9.0版本,引入 Componentlazy

5.x/4.x

  • roadmap
  • 2017年3月发布的 v4.0.0 版本
  • 2019年3月 发布的 v5.0.0 版本,其实是4.4。由于4.3及以下版本的react-router-dom 的依赖"react-router": "^4.3.1",由于^,安装时会依赖安装最新4.4依赖,但其API并不兼容,所以不得不得升级大版本😂,你会发现5.x以后的依赖都是固定版本号的。
  • 2019年9月发布的 v5.1.0版本,引入了一些 hooks 以及<Route children>
  • 2022 年10月更新的 v5.3.4,目前 v5 的最新版本

3.x

  • 生命周期:v3.0.0(2016年10月) - v3.2.6(2020年3月)
  • css-tricks.com/learning-re...
  • 集中式配置的路由,v6 配置路由的方式其更像 v3
jsx 复制代码
const PrimaryLayout = props => (
  <div className="primary-layout">
    <header>router v3</header>
    <main>
      {props.children} // 嵌套路由
    </main>
  </div>
)

// v3
const App = () => (
  <Router history={browserHistory}>
    <Route path="/" component={PrimaryLayout}>
      <IndexRoute component={HomePage} />
      <Route path="/users" component={UsersPage} />
    </Route>
  </Router>
) 

而v4 不再使用集中式配置的方式,路由规则可以分散到页面和布局中,可实现动态路由。

jsx 复制代码
const PrimaryLayout = () => (
  <div className="primary-layout">
   <header>router v3</header>
    <main>
     <Switch>
        <Route path="/" exact component={HomePage} />
        <Route path="/users" component={UsersPage} />
      </Switch>
    </main>
  </div>
)

// v4
const App = () => (
  <BrowserRouter>
    <PrimaryLayout />
  </BrowserRouter>
)

v4 的路由匹配的特点(槽点):

  • 默认为包容性路由,多个<Router>匹配会同时渲染
  • 使用<Switch>包裹才会只匹配一个路由,但是按Route排列的先后顺序匹配的
  • 如果访问 /users ,path "/""/users"都会匹配,所以需要exact 以确保path 要完全匹配

react router

@reach/router 相比同期的 react-router 更简洁轻量,react-router v6 吸收了 reach-router 的很多想法,可以看作 reach-router 的 2.x 版本,所以 API 风格更接近 reach-router

最后一次更新为 2020 年 2 月发布的 1.3.4 版本

小结

React Router v6 is the successor(后续版本) to all previous versions of React Router including v3 and v4/5. It is also the successor to Reach Router.

react-router v6 就是吸收了 v3 v4/5 以及 @reach/router 的最佳想法,才有了现在的 v6。

  • 支持 v3 的集中配置嵌套路由,以及基于对象配置路由的方式,可读性更好。
  • 使用<Routes>代替 <Switch>,也无需指定exact<Routes>也可以定义多个;
  • 支持 reach router 的最佳路径匹配和相对路径等

从 v6.4 后又融合了 remix 框架了的 Data API,但不得不说 react-router 6.4 后越来越复杂了,如果想用个基本的路由功能,那可以先不用 Data API。

想了解 Data API 的可以先看这篇博客,个人感觉要用 Data API 自己用的话,不如就直接上 Remix 框架了( 9月14号刚发布了Remix v2)。

NPM包构成

根据平台选择

  • Web应用:react-router-dom
  • React Native:react-router-native

两者都依赖react-router核心包,不必手动安装。

最底层依赖包:

v6.4 之前的是依赖的 history,之后依赖的@remix-run/router,和 remix (v1.8.0+) 共用,代替了history,也就支持了 Data API

创建路由的方式

安装

bash 复制代码
npm i react-router-dom

路由器分为 Browser、Hash、Memory、Static 类型的路由器,web 端常用的路由类型就是 history 和 hash,本文以 history 路由为例讲解,也就是 BrowserRouter。

主要是分为两种创建方式:

  • 使用 createBrowserRouter 函数,v6.4 推出的支持 Data API
  • 使用 <BrowserRouter> 组件,经典方式,不支持 Data API

使用createBrowserRouter函数

createXXRouter是在 v6.4.0 版本推出的,官方称为 Data Router,支持 Data API 。

最新的官网是使用createBrowserRouter 创建 Router 实例,传入RouterProvider 使用。

jsx 复制代码
// App.jsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
// import route components

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "/about",
    element: <About />,
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

默认是 JS 对象形式创建的,如果习惯使用 JSX 创建,则需要套上createRoutesFromChildren/createRoutesFromElements

jsx 复制代码
const router = createBrowserRouter(
  createRoutesFromChildren(
    <>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </>
  )
);

使用 RouterProvider 时,如果声明 React 元素element: <Home />但并不需要传参,可以使用声明组件的方式Component: Home,看起来更简洁。

使用<BrowserRouter>组件

要用BrowserRouter包裹Routes,用来提供上下文。

<Routes>包裹多个<Route>,当路径改变时<Routes> 会检查其所有 <Route> 找到最佳匹配,并渲染出来。

  • 经典的JSX方式:
jsx 复制代码
import { BrowserRouter, Routes, Route } from "react-router-dom";
// import route components

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}
  • 如果喜欢用 JS 对象的方式声明路由则可以用 useRoutes,在 v5 版本中要使用 react-router-config 包才可以。
jsx 复制代码
import { BrowserRouter, useRoutes } from "react-router-dom";
// import route components

function Routes() {
  return useRoutes([
    {
      path: "/",
      element: <Home />,
    },
    {
      path: "/about",
      element: <About />,
    },
  ]);
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes />
    </BrowserRouter>
  );
}

使用钩子 useRoutes 其实就是 <Routes> 的替代 ,只是风格不同。<Routes>就是基于useRoutes 实现的。

jsx 复制代码
export function Routes({
  children,
  location,
}: RoutesProps): React.ReactElement | null {
  return useRoutes(createRoutesFromChildren(children), location);
}

路由匹配

:动态参数

jsx 复制代码
function Team() {
  let params = useParams();
  console.log(params.teamId); // "hotspur"
}

<Route
  // this path will match URLs like
  // - /teams/hotspur
  path="/teams/:teamId"
  element={<Team />}
/>;

?可选参数

jsx 复制代码
<Route
  // this path will match URLs like
  // - /categories
  // - /en/categories
  path="/:lang?/categories"
  element={<Categories />}
/>;

function Categories() {
  let params = useParams();
  console.log(params.lang);
}

* 通配符

通配符只能在path末尾使用

jsx 复制代码
<Route
  // this path will match URLs like
  // - /files
  // - /files/one
  // - /files/one/two
  // - /files/one/two/three
  path="/files/*"
  element={<Team />}
/>;

// and the element through `useParams`
function Team() {
  let params = useParams();
  console.log(params["*"]); // "one/two"
}

path="*",可用于404页面

jsx 复制代码
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="*" element={<NotFound />} />
</Routes>

嵌套路由

相对路径

path 不以/开头的路径为相对路径:

  • <Route path> 相对于父路由,不必补全path
  • <Link to> 相对父路由

当然 v6 也支持绝对路径

Outlet

嵌套<Route>的情况下,需要使用 Outlet 渲染子路由,相当于占位元素。相比 v3 用的 props.children 更直观。

jsx 复制代码
<Routes>
    <Route path="parent" element={<Parent />}>
        <Route path="child" element={<Child />} />
    </Route>
</Routes>

function Parent() {
    return (
        <div>
            <h1>Parent Component</h1>
            <Outlet />  {/* 当访问 "parent/child" 时,Child 组件会在这里渲染 */}
        </div>
    );
}

如果父路由不指定element 则默认为<Outlet>

布局路由

父路由不指定path时,仅作为布局,子路由元素将在Outlet的位置显示。

jsx 复制代码
<Route element={<PageLayout />}>
  <Route path="/" element={<Section content="Home" />} />
  <Route path="/about" element={<Section content="About" />} />
</Route>

function PageLayout({ children }) {
    return (
        <div>
            <header>Header Section</header>
            <Outlet />  {/* 子路由 (如 Section) 会在这里渲染 */}
        </div>
    );
}

索引路由

没有path的子路由;访问父路由时渲染,如访问/teams 时将渲染TeamsIndex

jsx 复制代码
<Route path="/teams" element={<Teams />}>
  <Route index element={<TeamsIndex />} />
  <Route path=":id" element={<Team />} />
</Route>

多级Routes

v6 也可以定义多级Routes, 但父Routepath要以*结尾才会匹配

javascript 复制代码
  <Routes>
    <Route path="blog/**" element={<Blog />} />
  </Routes>;

  function Blog() {
    return (
      <Routes>
        <Route path="post/:id" element={<Post />} />
      </Routes>
    );
  }

hooks

useNavigate()

  • 用于编程方式导航,具体看文档
jsx 复制代码
function Nav() {
  const navigate = useNavigate();

  return (
    <div>
      <button onClick={() => navigate("/")}>home</button>
      <button onClick={() => navigate("/about")}>about</button>
    </div>
  );
}

useParams()

  • 获取动态参数的对象
jsx 复制代码
function ProfilePage() {
  // Get the userId param from the URL.
  let { userId } = useParams();
  // ...
}

function App() {
  return (
    <Routes>
      <Route path="users">
        <Route path=":userId" element={<ProfilePage />} />
        <Route path="me" element={...} />
      </Route>
    </Routes>
  );

useSearchParams()

  • 用于读取和修改当前url的查询字符串,返回 searchParams 对象
jsx 复制代码
const [searchParams, setSearchParams] = useSearchParams();

useLocation()

  • 返回 Location 对象
jsx 复制代码
// history栈中的一条记录
interface Location {
  pathname: string; // 继承自Path
  search: string; // 继承自Path
  hash: string; // 继承自Path
  state: unknown; // 当前 location 关联的任意值
  key: string; // 当前 location 关联的唯一字符串
}

组件

  • <Link /> <NavLink /> 用于导航,
  • to可指定绝对路径(/开头)和相对路径,相对路径可以像cd命令一样用,如..
  • NavLinkLink基础上实现,可判断当前路由是否被激活,具体看文档
jsx 复制代码
<NavLink
  to="about"
  className={({ isActive, isPending }) => {
    return isActive ? "active" : isPending ? "pending" : "";
  }}
/>
  • <Navigate /> 用于路由跳转,当渲染该组件时,立即跳转指定路由;可用于权限判断的重定向。
jsx 复制代码
function App() {
  return (
    <Routes>
      <Route path="/public" element={<PublicPage />} />
      <Route
        path="/protected"
        element={
          <RequireAuth redirectTo="/login">
            <ProtectedPage />
          </RequireAuth>
        }
      />
    </Routes>
  );
}

function RequireAuth({ children, redirectTo }) {
  let isAuthenticated = getAuth();
  return isAuthenticated ? children : <Navigate to={redirectTo}  replace={true} />;
}

懒加载

使用React.lazy() 和动态 import()以及Suspense 实现,可自己封装成一个 Wrapper 使用

jsx 复制代码
const About = React.lazy(() => import("./pages/about"));


<Route
  path="/about"¬
  element={
    <React.Suspense fallback={<Loading />}>
      <About />
    </React.Suspense>
  }
/>

链接

相关推荐
-seventy-6 分钟前
对 JavaScript 原型的理解
javascript·原型
&白帝&23 分钟前
uniapp中使用picker-view选择时间
前端·uni-app
谢尔登30 分钟前
Babel
前端·react.js·node.js
ling1s30 分钟前
C#基础(13)结构体
前端·c#
卸任37 分钟前
使用高阶组件封装路由拦截逻辑
前端·react.js
lxcw1 小时前
npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED
前端·npm·node.js
秋沐1 小时前
vue中的slot插槽,彻底搞懂及使用
前端·javascript·vue.js
这个需求建议不做1 小时前
vue3打包配置 vite、router、nginx配置
前端·nginx·vue
QGC二次开发1 小时前
Vue3 : Pinia的性质与作用
前端·javascript·vue.js·typescript·前端框架·vue
云草桑1 小时前
逆向工程 反编译 C# net core
前端·c#·反编译·逆向工程