前言
看了一圈 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版本,引入
Component
、lazy
等
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
, 但父Route
的path
要以*
结尾才会匹配
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
<Link />
和<NavLink />
用于导航,to
可指定绝对路径(/
开头)和相对路径,相对路径可以像cd
命令一样用,如..
NavLink
在Link
基础上实现,可判断当前路由是否被激活,具体看文档。
jsx
<NavLink
to="about"
className={({ isActive, isPending }) => {
return isActive ? "active" : isPending ? "pending" : "";
}}
/>
Navigate
<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>
}
/>