React Router v6新特性简介及迁移
掘金地址:https://juejin.cn/post/7053031843376922660
如需转载,请注明出处
简介
React Router库包含三个不同的npm包,以下每个包都有不同的用途。
- react-router :核心库,包含 React Router 的大部分核心功能,包括路由匹配算法和大部分核心组件和钩子。
- react-router-dom:React应用中用于路由的软件包,包括react-router的所有内容,并添加了一些特定于 DOM 的 API,包括BrowserRouter、HashRouter和Link。
- react-router-native: 用于开发React Native应用,包括react-router的所有内容,并添加了一些特定于 React Native 的 API,包括NativeRouter和Link。
版本
以下v5从5.1.2所有版本都列出了。v6的还有很多alpha和beta小版本没有列出。
新项目目前用了6.0.2,之前旧项目用的是5.1.2。
react-router-dom npm地址
版本 | 下载量 | 发布时间(对比日期2022.1.14) |
---|---|---|
6.2.1 | 818,595 | 1个月前 |
6.0.2 | 464,961 | 2个月前 |
6.0.0 | 5,540 | 2个月前 |
5.3.0 | 1,612,985 | 4个月前 |
5.2.1 | 68,038 | 5个月前 |
6.0.0-beta.0 | 62,966 | 2年前 |
5.2.0 | 1,734,184 | 2年前 |
6.0.0-alpha.0 | 7 | 2年前 |
5.1.2 | 462,691 | 2年前 |
相比v5,打包后的包大小,从20.8k 减少到 10.8k。包分析网站
image.png image.png安装
// npm 安装
npm install react-router-dom@6
// yarn 安装
yarn add react-router-dom@6
启用全局路由模式
全局路由有常用两种路由模式可选:HashRouter 和 BrowserRouter 分别是hash模式和history模式。
采用BrowserRouter:
import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
);
App.jsx 设置路由
import './App.css';
import { Routes, Route, Link } from "react-router-dom"
function App() {
return (
<div className="App">
<header className="App-header">
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/about" element={<About />}></Route>
</Routes>
</header>
</div>
);
}
function Home() {
return <div>
<main>
<h2>Welcome to the homepage</h2>
</main>
<nav>
<Link to="/about">about</Link>
</nav>
</div>
}
function About() {
return <div>
<main>
<h2>Welcome to the about page</h2>
</main>
<nav>
<ol>
<Link to="/">home</Link>
<Link to="/about">about</Link>
</ol>
</nav>
</div>
}
export default App;
写在前面
如果您刚刚开始使用 React Router,或者您想在新应用中试用 v6,请参阅入门指南。
以下内容主要叙述的是与React Router v5版本对比的新特性及如何进行快速迁移。
升级前准备
1. 升级到React v16.8
React Router v6 大量使用React hooks,因此在尝试升级到 React Router v6 之前,您需要使用 React 16.8 或更高版本。好消息是 React Router v5 与 React >= 15 兼容,因此如果您使用的是 v5(或 v4),您应该能够在不接触任何路由器代码的情况下升级 React。
2. 升级到React Router v5.1(非必要)
如果您先升级到 v5.1,则切换到 React Router v6 会更容易。因为在 v5.1 中,发布了对元素处理的增强,<Route children>这将有助于平滑过渡到 v6。不要使用<Route component>和<Route render>props,而是在任何地方使用常规元素<Route children>并使用钩子来访问路由器的内部状态。
升级到 React Router v6
- <Switch>重命名为<Routes>。
- <Route>的新特性变更。
- 嵌套路由变得更简单。
- 用useNavigate代替useHistory。
- 新钩子useRoutes代替react-router-config。
1. <Switch>
重命名为<Routes>
React Router v6 引入了一个Routes的组件,类似于以前的Switch,但功能更强大。它包括相对路由和链接、自动路由排名、嵌套路由和布局等功能。
// v5
<Switch>
<Route exact path="/"><Home /></Route>
<Route path="/profile"><Profile /></Route>
</Switch>
// v6
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile/*" element={<Profile />} />
</Routes>
2.<Route>
的新特性变更
Route负责渲染React组件的UI。在v6中会用到两个属性:
- path:与当前页面对应的URL匹配。
- element:这个是新增的,用于决定路由匹配时,渲染哪个组件。在v5的时候,我们通常会用到component这个属性,或者是render。
简单来说,component/render被element替代
// v5
<Route path=":userId" component={Profile} />
<Route
path=":userId"
render={routeProps => (
<Profile routeProps={routeProps} animate={true} />
)}
/>
// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />
3. 嵌套路由更简单
嵌套路由在实际应用中很常见,如以下两个页面,User这个组件是共用的,切换路由的时候,仅改变Profile和Posts子组件。
image.png在React Router v5中,必须明确定义嵌套路由,React Router v6并非如此。它从React Router库中挑选了一个名为 Outlet 的最佳元素,为特定路由呈现任何匹配的子元素。用起来和Vue Router里的<router-view>差不多。
3.1 Outlet实现嵌套路由
- v5中实现嵌套路由
// v5
import {
BrowserRouter,
Switch,
Route,
Link,
useRouteMatch
} from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
);
}
function Profile() {
let { path, url } = useRouteMatch();
return (
<div>
<nav>
<Link to={`${url}/me`}>My Profile</Link>
</nav>
<Switch>
<Route path={`${path}/me`}>
<MyProfile />
</Route>
<Route path={`${path}/:id`}>
<OthersProfile />
</Route>
</Switch>
</div>
);
}
- v6中实现嵌套路由,可以删除字符串匹配逻辑。不需要任何useRouteMatch()。利用新API:Outlet( 利用下面第5点说到的useRoutes,可以进一步简化。 )
import { Outlet } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="profile" element={<Profile />}>
<Route path=":id" element={<MyProfile />} />
<Route path="me" element={<OthersProfile />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function Profile() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav>
{/*将直接根据上面定义的不同路由参数,渲染<MyProfile />或<OthersProfile /> */}
<Outlet />
</div>
)
}
3.2 多个<Routes />
以前,我们只能 在React App中使用一个 Routes。但是现在我们可以在React App中使用多个路由,这将帮助我们基于不同的路由管理多个应用程序逻辑。
import React from 'react';
import { Routes, Route } from 'react-router-dom';
function Dashboard() {
return (
<div>
<p>Look, more routes!</p>
<Routes>
<Route path="/" element={<DashboardGraphs />} />
<Route path="invoices" element={<InvoiceList />} />
</Routes>
</div>
);
}
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="dashboard/*" element={<Dashboard />} />
</Routes>
);
}
4. 用useNavigate代替useHistory
从一目了然改到双目失明。。。
// v5
import { useHistory } from 'react-router-dom';
function MyButton() {
let history = useHistory();
function handleClick() {
history.push('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
现在,history.push()将替换为navigation()
// v6
import { useNavigate } from 'react-router-dom';
function MyButton() {
let navigate = useNavigate();
function handleClick() {
navigate('/home');
};
return <button onClick={handleClick}>Submit</button>;
};
history的用法也将被替换成:
// v5
history.push('/home');
history.replace('/home');
// v6
navigate('/home');
navigate('/home', {replace: true});
5. 用新钩子useRoutes代替react-router-config
这个hooks用完,比上面直接写router组件的jsx文件要简洁很多
function App() {
let element = useRoutes([
{ path: '/', element: <Home /> },
{ path: 'dashboard', element: <Dashboard /> },
{ path: 'invoices',
element: <Invoices />,
children: [
{ path: ':id', element: <Invoice /> },
{ path: 'sent', element: <SentInvoices /> }
]
},
// 404找不到
{ path: '*', element: <NotFound /> }
]);
return element;
}
项目实践
以下例子都基于useRoutes
tips:例子中fullPath这个属性是自己定义的非官方属性,因为嵌套路由的时候,path只需要写相对的路径。为了方便直接看到包含父路径的完整路径,所以定义这个用于查看,实际React Router并不会用到这个属性。
1. 懒加载
import { useRoutes } from 'react-router-dom'
// 懒加载
const lazyLoad = (path: string) => {
const Comp = React.lazy(() => import(`@page/${path}`))
return (
<React.Suspense fallback={<>加载中...</>}>
<Comp />
</React.Suspense>
)
}
const routers = [
{
name: '项目',
path: '/',
element: <Backbone />,
children: [
{
name: '首页',
path: 'index/*',
// 这个属性实际路由渲染不会用到。
fullPath: '/index',
element: lazyLoad('IndexPage')
}
]
]
const Index = () => useRoutes(routers)
export default Index
2. Index路由:默认子路由
用于嵌套路由,仅匹配父路径时,设置渲染的组件。 解决当嵌套路由有多个子路由但本身无法确认默认渲染哪个子路由的时候,可以增加index属性来指定默认路由。
index路由和其他路由不同的地方是它没有path属性,他和父路由共享同一个路径。
例如: 以下路由设置了两个子路由,分别是/foo/invoices 和 /foo/activity 。但是当页面直接访问 /foo的时候,页面就不知道怎么渲染了。这时候可以通过index设置默认展示到invoices。
const routers = [
{
name: '项目',
path: '/foo',
element: <Layout />,
children: [
// 仅匹配到父级路由时, 设置index为true,不需要指定path
{ index: true, element: lazyLoad('Invoices') },
{
name: '首页',
path: 'invoices',
element: lazyLoad('Invoices')
},
{
name: 'about',
path: 'activity',
element: lazyLoad('Activity')
}
]
}
]
3. 404
废弃了V5中的Redirect
// v5 废弃了
const routers = [
{ path: 'home', redirectTo: '/' }
]
// 404可以这么写
const routers = [
{
name: '404',
path: '*',
element: <NoMatch />
}
]
4. 动态路由
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如新闻详情页,对应不同新闻,都对应到News组件,路径可能为 news/1,news/2...。其中数字1为新闻的id。这个是动态变化。
- 动态路径参数 以冒号开头
const routers = [
{
name: '公告详情页',
// 动态路径参数 以冒号开头
path: 'noticeDetail/:id',
fullPath: '/noticeDetail',
element: lazyLoad('NoticeDetail')
},
{
name: '帮助中心详情页',
path: 'helpCenterDetail/:fid/:sid',
fullPath: '/helpCenterDetail',
element: lazyLoad('HelpCenterDetail')
}
]
-
useParams 用于组件获取动态路径的参数
例如上面这个例子,有两个动态参数fid和sid,在HelpCenterDetail组件:
import { useParams } from 'react-router-dom'
const { fid, sid } = useParams()
5. 路由通配符
支持以下几种通配符号。注意:这里的*只能用在/后面,不能用在实际路径中间
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*
以下这些v6里面不支持
/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*
6. url搜索参数
useSearchParams的使用。
例如 :http://URL/user?id=111,获取id对应的值
import { useSearchParams } from 'react-router-dom'
const [searchParams, setSearchParams] = useSearchParams()
// 获取参数
searchParams.get('id')
// 判断参数是否存在
searchParams.has('id')
// 同时页面内也可以用set方法来改变路由
setSearchParams({"id":2})
7. withRouter
v6不再提供withRouter,略坑。
官方解释:这个问题通常源于您使用的是不支持钩子的 React 类组件。在 React Router v6 中,我们完全接受了钩子并使用它们来共享所有路由器的内部状态。但这并不意味着您不能使用路由器。假设您实际上可以使用钩子(您使用的是 React 16.8+),您只需要一个包装器。
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
项目中完整例子
import { useRoutes } from 'react-router-dom'
import Backbone from '../layouts/Backbone'
import InfoPage from '../pages/InfoPage/index'
import DataCenterPage from '../pages/DataCenterPage/index'
import NoMatch from './NoMatch'
// 懒加载
const lazyLoad = (path: string) => {
const Comp = React.lazy(() => import(`@page/${path}`))
return (
<React.Suspense fallback={<>加载中...</>}>
<Comp />
</React.Suspense>
)
}
const routers = [
{
name: '项目',
path: '/',
element: <Backbone />,
children: [
// 仅匹配到父级路由时
{ index: true, fullPath: '/', element: lazyLoad('IndexPage') },
{
name: '首页',
path: 'index/*',
fullPath: '/index',
element: lazyLoad('IndexPage')
},
{
name: '消息中心',
path: 'info/',
// element: lazyLoad('InfoPage'),
element: <InfoPage />,
children: [
{ index: true, fullPath: '/info', element: lazyLoad('NoticeList') },
{
name: '公告',
path: 'noticeList',
fullPath: '/info/noticeList',
element: lazyLoad('NoticeList')
},
{
name: '帮助中心',
path: 'helpCenter',
fullPath: '/info/helpCenter',
element: lazyLoad('HelpCenter')
}
]
},
{
name: '我要推广',
path: 'activityList/*',
fullPath: '/activityList',
element: lazyLoad('ActivityList')
},
{
name: '数据中心',
path: 'data/',
// element: lazyLoad('InfoPage'),
element: <DataCenterPage />,
children: [
{ index: true, fullPath: '/data', element: lazyLoad('Order') },
{
name: '订单查询',
path: 'order',
fullPath: '/data/order',
element: lazyLoad('Order')
},
{
name: '收益查询',
path: 'reward',
fullPath: '/data/reward',
element: lazyLoad('Reward')
},
{
name: '结算查询',
path: 'settlement',
fullPath: '/data/settlement',
element: lazyLoad('Settlement')
}
]
}
]
},
{
name: '公告详情页',
// 动态路由
path: 'noticeDetail/:id',
fullPath: '/noticeDetail',
element: lazyLoad('NoticeDetail')
},
{
name: '帮助中心详情页',
path: 'helpCenterDetail/:fid/:sid',
fullPath: '/helpCenterDetail',
element: lazyLoad('HelpCenterDetail')
},
{
name: '个人注册页',
path: 'register/person',
fullPath: '/register/person',
element: lazyLoad('PersonBaseInfo')
},
{
name: '404',
path: '*',
element: <NoMatch />
}
]
const Index = () => useRoutes(routers)
export default Index
常用路由组件和hooks
完整API: https://reactrouter.com/docs/en/v6/api
组件名 | 作用 | 说明 |
---|---|---|
<Routers> |
一组路由 | 代替原有<Switch> ,所有子路由都用基础的Router children来表示 |
<Router> |
基础路由 | Router是可以嵌套的,解决原有V5中严格模式 |
<Link> |
导航组件 | 在实际页面中跳转使用 |
<Outlet/> |
自适应渲染组件 | 根据实际路由url自动选择组件,主要用于嵌套路由,类似Vue Router中的<router-view>
|
hooks名 | 作用 | 说明 |
---|---|---|
useParams | 返回当前参数 | 根据路径读取参数 |
useNavigate | 返回当前路由 | 代替原有V5中的 useHistory |
useOutlet | 返回根据路由生成的element | |
useLocation | 返回当前的location 对象 | |
useRoutes | 同Routers组件一样,只不过是在js中使用 | 代替react-router-config |
useSearchParams | 用来匹配URL中?后面的搜索参数 |
参考文献
React Router网址: React Router
React Router文档: https://reactrouter.com/docs/en/v6
完整API: https://reactrouter.com/docs/en/v6/api
react-router-dom: npm地址
参考博客:https://blog.csdn.net/weixin_40906515/article/details/104957712
https://zhuanlan.zhihu.com/p/191419879
https://www.jianshu.com/p/03234215a90e
https://blog.csdn.net/zjjcchina/article/details/121921585
包大小分析网站: https://bundlephobia.com/