什么,React Router已经到V6了 ??
上周突然心血来潮,去 react-router 的 github 看了下,好家伙,最新版本居然已经到了 (已于2021.11.4发布 6.0.0 正式版),且全部用 ts 重写(表示好评!!),具体变化可看 Migrating React Router v5 to v6。不看不知道,一看吓一跳,发现变化还是挺大的,不过,很多 6.0.0-beta.8
api
也变得比之前好用。那么,下面我会尽可能提供各种例子介绍一些常用 api
的改动,让大家更快熟悉最新的用法。
<Switch>全部改为<Routes>
与 Switch
相比,Routes
的主要优点是:
Routes
内的所有<Route>
和<Link>
是相对的。这使得<Route path>
和<Link to>
中的代码更精简、更可预测- 路由是根据最佳匹配而不是按顺序选择的
- 路由可以嵌套在一个地方,而不是分散在不同的组件中(当然也可以写在子组件中),而且嵌套的 parent route 的
path
不用加*
import {
BrowserRouter,
Routes,
Route,
Link,
Outlet
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
{* 上面的优点一:path是相对的 *}
{* 上面的优点三:path 不用加'*' *}
<Route path="users" element={<Users />}>
{* 上面的优点二: 无需按顺序 *}
{* 上面的优点三: 路由可以嵌套在一个地方 *}
<Route path="me" element={<OwnUserProfile />} />
<Route path=":id" element={<UserProfile />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function Users() {
return (
<div>
<nav>
{* 上面的优点一: <Link to>是相对的 *}
<Link to="me">My Profile</Link>
</nav>
{* Outlet后面会讲 *}
<Outlet />
</div>
);
}
注意上面的第三点,嵌套的 parent route 的 path 不用加*。但如果不是嵌套,而是分散在子组件中,就需要尾部加上*
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
{* 不是嵌套就需要尾部加上* *}
<Route path="users/*" element={<Users />} />
</Routes>
</BrowserRouter>
);
}
function Users() {
return (
<Routes>
<Route element={<UsserLayout />}>
<Route path="me" element={<OwnUserProfile />} />
<Route path=":id" element={<UserProfile />} />
</Route>
</div>
);
}
function UsersLayout() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
<Link to="2">User Profile</Link>
</nav>
<Outlet />
</div>
);
}
<Route>
<Route element>
Route 的 render 或 component 改为 element
<Route path=":userId" element={<Profile animate={true} /> />
function Profile({ animate }) {
const params = useParams();
const location = useLocation();
}
通过这种形式:
- 可以向组件传 props,如上面的
animate={true}
- 因为有了 hook 的出现,所以不必再通过 renderProps 向组件传递路由的一些 props,我们可以通过
useParams
、useLocation
就可以拿到这些信息
当然还有另外一个重要的原因是因为 v6
Route 的 children 是用于嵌套路由,如下
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="users" element={<Users />}>
<Route path="me" element={<OwnUserProfile />} />
<Route path=":id" element={<UserProfile />} />
</Route>
</Routes>
</BrowserRouter>
);
}
那么我们要显示 OwnUserProfile
组件的话通过 /users/me
就可以定位到了,这看起来相当的直观
<Route path>
v6
简化了 path
的格式,只支持两种动态占位符:
:id
样式参数*
通配符,只能在 path 的末尾使用,如users/*
举个 🌰
以下的 path 是正确的:
path = '/groups'
path = '/groups/admin'
path = '/users/:id'
path = '/users/:id/messages'
path = '/files/*' // 通配符放在末尾
path = '/files/:id/*'
path = '/files-*'
以下的 path 是错误的:
path = '/users/:id?' // ? 不满足上面两种格式
path = '/tweets/:id(\d+)' // 有正则,不满足上面两种格式
path = '/files/*/cat.jpg'// 通配符不能放中间
<Route caseSensitive>
caseSensitive: boolean
,用于正则匹配 path 时是否开启 ignore 模式,即匹配时是否忽略大小写
源码中部分
// 根据path生成是regexpSource是否要ignore
const matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
<Route index>
index
即表示是否是主路由,如果设置为 true 的话不能有 children,如下面的<Route index element={<Home />} />
function App() {
return (
<Routes>
<Route path='/' element={<Layout />}>
<Route path='auth/*' element={<Auth/> } />
<Route path='basic/*' element={<Basic/> } />
</Route>
</Routes>
)
}
function Home() {
return (
<h2> Home </h2>
)
}
function Basic() {
return (
<div>
<h1>Welcome to the app!</h1>
<h2>下面()中的就是真实的Link组件</h2>
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />}/>
...
</Route>
</Routes>
</div>
);
}
那么/basic
会自动显示<Home />
<Layout /> 与 <Outlet />
上面的很多例子中都有 Layout,这个究竟有什么用?和Outlet
又有什么关系?
我们看下代码(和下面的图片搭配看效果更佳)
function App() {
return (
<Routes>
<Route path='/' element={<Layout />}>
<Route path='auth/*' element={<Auth/> } />
<Route path='basic/*' element={<Basic/> } />
</Route>
</Routes>
)
}
// 下图的最外面的红色框
function Layout() {
return (
<>
<p>主页面</p>
<ul>
<li>
<Link to='auth'>auth</Link>
</li>
<li>
<Link to='basic'>basic</Link>
</li>
</ul>
<hr />
{* 下图蓝色框 *}
<Outlet />
</>
)
}
function Basic() {
return (
<div>
<h1>Welcome to the app!</h1>
<h2>下面()中的就是真实的Link组件</h2>
<Routes>
<Route element={<BasicLayout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />}/>
...
</Route>
</Routes>
</div>
);
}
// 下图绿色框
function BasicLayout() {
return (
<div>
<ul>
...
<Link to=".">Home({` <Link to=".">`})</Link>
...
</li>
</ul>
<Outlet />
</div>
);
}
// 下图黄色框
function Home() {
return (
<h2>Home</h2>
)
}
从代码和上面示意图我们可以得知,组件<Layout/>
一般作为 parent route 的element,除了渲染一些公共的UI, 还有用于渲染子路由的<Outlet/>
, 而代码中的<Outlet/>
渲染的是<Basic/>
,<Basic>
的<BasicLayout />
又有<Outlet/>
,但其渲染的是<Route element={<BasicLayout />}>
匹配到路由的 element。
从上面我们可以得到的结论是:
组件 <Layout/>
一般作为 parent route 的element,除了渲染一些公共的UI外, 其中的<Outlet/>
的作用类似插槽,用于匹配子路由的 element
useRoutes
上面我们都是把 Route 作为 Routes 的 children
function App() {
return (
<Routes>
<Route path='/' element={<Layout />}>
<Route path='auth/*' element={<Auth/> } />
<Route path='basic/*' element={<Basic/> } />
</Route>
</Routes>
)
}
但是我们还可以通过 useRoutes
生成对应的 element
import { useRoutes } from 'react-router-dom'
function App() {
const element = useRoutes([
{
path: '/',
element: <Layout />,
children: [
{
path: 'auth/*',
element: <Auth/>
},
{
path: 'basic/*',
element: <Basic/>
}
]
}
])
return (
{element}
)
}
这种配置项让我们可以更清晰地看出路由的嵌套结构
<Link>
<Link to>
在v5
中,如果 to
不以 /
开头的话会让人有点迷,因为这取决于当前的 URL。比如当前 URL 是/user
, 那么<Link to="me">
会渲染成 <a href="/me">
; 而如果是 /users/
,那么又会渲染成 <a href="/users/me">
。
那么,在 v6
中解决了上面那种让人迷惑的现象。
在 v6
中,无论当前 URL 是 /user
还是 /users/
, <Link to="me">
都会渲染成 <a href='/user/me'>
。
也就是说 to
更像我们常用的 cd
命令行,我们看下更多例子
<Route path="app">
<Route path="dashboard">
<Route path="stats" />
</Route>
</Route>
// 当前 URL 为 /app/dashboard 或 /app/dashboard/
<Link to="stats"> => <a href="/app/dashboard/stats">
<Link to="../stats"> => <a href="/app/stats">
<Link to="../../stats"> => <a href="/stats">
<Link to="../../../stats"> => <a href="/stats">
// 命令行中, 当前目录为 /app/dashboard
cd stats # pwd is /app/dashboard/stats
cd ../stats # pwd is /app/stats
cd ../../stats # pwd is /stats
cd ../../../stats # pwd is /stats
有一点要特别注意,当 <Route path>
匹配多个 URL 片段时, <Link to="..">
不总是渲染为 <a href="..">
, 这种情况下的 to='..'
是基于父级 Route 的 path,例子如下
function App() {
return (
<Routes>
<Route path="users">
<Route
path=":id/messages"
element={
// 最终是/users, 而不是/:id
<Link to=".." />
}
/>
</Route>
</Routes>
);
}
如果还不理解,我们可以举个极端例子,比如 route 的 path 为basic/*
,当前 URL 为/basic/auth/home
,如下代码
function App() {
return (
<Routes>
<Route path="/">
<Route path="auth/*" element={<Auth />} />
<Route
path="basic/*"
element={
...
<Link to="../auth" />
...
}
/>
</Route>
</Routes>
);
那么上面的 to="../auth"
是要跳转到 /basic/auth
还是/auth
(答案是/auth
)?所以为了避免 *
通配符可以匹配多种路径的情况,统一为 to='..'
是基于父级 Route 的 path
<Link state>
即点击后可以给 to
传对应的 state
<Link replace>
replace:boolean
,默认 false,即跳转路由要用 push
还是 replace
<Link target>
target 类型为
type HTMLAttributeAnchorTarget =
| '_self'
| '_blank'
| '_parent'
| '_top'
| (string & {});
这个其实我们几乎没用到,这里只简单介绍下可以传入的值
useHistory 被干掉了,换成了 useNavigate
使用方法如下:
// v6
import { useNavigate } from "react-router-dom";
function App() {
const navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
naviaget(to)默认就是 history.push
// v6
navigate('/home');
//v5
history.push('/home')
naviaget(to, { replace: true })就是 history.replace
// v6
navigate('/home', { replace: true });
//v5
history.replace('/home')
naviaget(to: number)就是 history.go
// v6
import { useNavigate } from "react-router-dom";
function App() {
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-2)}>
Go 2 pages back
</button>
<button onClick={() => navigate(-1)}>Go back</button>
<button onClick={() => navigate(1)}>
Go forward
</button>
<button onClick={() => navigate(2)}>
Go 2 pages forward
</button>
</>
);
}
// v5
import { useHistory } from "react-router-dom";
function App() {
const { go, goBack, goForward } = useHistory();
return (
<>
<button onClick={() => go(-2)}>
Go 2 pages back
</button>
<button onClick={goBack}>Go back</button>
<button onClick={goForward}>Go forward</button>
<button onClick={() => go(2)}>
Go 2 pages forward
</button>
</>
);
}
naviagete 与<Link to>一样, 输入参数类似 cd 命令行
<Route path="app">
<Route path="dashboard">
<Route path="stats" />
</Route>
</Route>
// 当前 URL 为 /app/dashboard 或 /app/dashboard/
<Link to="stats"> => <a href="/app/dashboard/stats">
<Link to="../stats"> => <a href="/app/stats">
<Link to="../../stats"> => <a href="/stats">
<Link to="../../../stats"> => <a href="/stats">
// 当前 URL 为 /app/dashboard 或 /app/dashboard/
const navigate = useNavigate()
navigate('stats') => '/app/dashboard/stats'
navigate('../stats') => '/app/stats'
navigate('../../stats') => '/stats'
navigate('../../../stats') => '/stats'
// 命令行中, 当前目录为 /app/dashboard
cd stats # pwd is /app/dashboard/stats
cd ../stats # pwd is /app/stats
cd ../../stats # pwd is /stats
cd ../../../stats # pwd is /stats
结语
好了,到了这里也差不多了,我们总结下一些变化:
<Switch>
全部改为<Routes>
Route
的render
和component
改为element
,且可以嵌套路由Route
还可以通过传入嵌套数组给useRoutes生成对应的elementto
、navigate
、path
不以/
开头都是相对路径,与cd
命令行类似<Outlet />
是个插槽,可以渲染匹配到的子路由
如果还想了解更多例子,可以打开react-router-source-analysis,有更多的例子。
到目前为止,这应该、大概、也许算国内最新的 React-Router 的源码分析了吧,有兴趣的可以看看,觉得可以的话请不要吝啬您的⭐ 。
最后
之后应该会写 React-Router
最新的源码分析文章,如果我不鸽的话🐶🐶,哈哈。
感谢留下足迹,如果您觉得文章不错😄😄,还请动动手指😋😋,点赞+收藏+转发🌹🌹
往期文章
转载自:https://juejin.cn/post/7025418839454122015