React Router 6
路由的概念,可以想像一下路由器,当来了一个请求时,路由器做了什么事情?它会把请求的IP地址和路由表进行匹配,匹配成功后,进行转发,直到目标主机。可以看到路由有三部分组成,一个是请求,一个是路由表,一个是匹配转发。对应到前端路由也是一个道理,只不过前端路由是拦截请求,显示不同的页面内容。首先要发起请求,说明你要到哪里去,React Router中定义了<Link>。其次要定义一个路由表,列出匹配规则和匹配成功后要显示什么,就是React Router中一条条的<Route>。最后就是来了请求时进行动态匹配和转发,React提供了<BrowserRouter>和<Routes>。<BrowserRouter> 把<Routes>包起来,<Routes>把<Route>包起来,来了请求,它就能匹配路由表。
<Link> 有一个to属性,就是标明去哪里
<Link to="/home">Home</Link>
<Route> 有两个属性,一个是path,就是列出匹配规则,一个是element,就是匹配成功后要显示什么内容。如果请求‘/home’,就显示<Home>组件内容,就可以如下定义
<Route path=’/home’, element={<Home/>} /> // 要写好Home 组件。
要注意的是elemet 接受的真是React Element。这里只是定义一条路由,路由表里的一条记录。当要匹配其它请求时,还要再写路由, 比如About
<Route path=’/about’, element={<About/>} />
要匹配多少请求,就要写多少条路由。这样一条条的路由就定义好了,放到<BrowserRouter>和<Routes>下面,只要来了请求,就能进行动态匹配。<BrowserRouter>提供页面的URL(Provides the cleanest URLs)。<Routes>进行动态匹配。
使用create-react-app创建项目router-tutorial,然后cd router-tutorial 并npm install react-router-dom。 在index.js中引入BrowserRouter 和<Routes>, BrowserRouter把Route包起来。整个index.js如下
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { Home, About, Contact, Products, Events } from "./pages"; ReactDOM.render(
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/contact' element={<Contact />} />
<Route path='/products' element={<Products />} />
<Route path='/events' element={<Events />} />
</Routes>
</BrowserRouter>,
document.getElementById('root')
);
为了让路由起作用,还要创建Home, About 等组件。在src目录下,新建一个pages.js文件,内容如下:
import React from 'react' // 首页内容
export const Home = () => (
<section className="home">
<h1>企业网站</h1>
<p>首页内容</p>
</section>
) // 企业事件内容
export const Events = () => (
<section className="events">
<h1>企业大事件</h1>
</section>
) // 公司产品
export const Products = () => (
<section className="products">
<h1>公司产品:手机、电脑</h1>
</section>
) // 联系我们
export const Contact = () => (
<section className="contact">
<h1>联系我们</h1>
<p>公司电话:0755 - 12345678</p>
</section>
) // 关于我们
export const About = () => (
<section className="about">
<h1>公司理念</h1>
<p>公司以人为本</p>
</section>
)
npm start,localhost:3000
怎么访问其它页面的内容呢?使用<Link />, <Link>表示要到哪里去,只要设置它的to属性和Route中的path属性一一对应就可以了,如<Link to=’/about’>关于我们</Link>,就表示要到/about下面。Home组件中增加四个<Link>
import { Link } from "react-router-dom";
export const Home = () => (
<section className="home">
<h1>企业网站</h1>
<p>首页内容</p>
<nav>
{/* 添加了四个导航组件Link */}
<Link to='/about'>关于我们</Link>
<Link to='/events'>企业事件</Link>
<Link to='/products'>公司产品</Link>
<Link to='/contact'>联系我们</Link>
</nav>
</section>
)
这时,点击不同的link 就去到不同页面,同时它还会改变地址栏,这时如要在地址栏中随便输入一个路径,页面一片空白,因为没一个路由和它匹配。最好写一个匹配不成功的路由,来处理一下这种情况。那路由的path怎么写?用“*”。要显示的组件可以随便写一下,在pages.js 下面再写一个组件,
export const NotFound404 = () =>(
<div className="whoops-404">
<h1>没有页面可以匹配</h1>
</div>
)
路由就是
<Route path='*' element={<NotFound404/>}></Route>
和普通路由一样,把它加到<Routes>组件下面。*表示,其它路由都不匹配的时候,才匹配它。
<BrowserRouter>
<Routes>
<Route path='/' element={<Home/>} />
<Route path='/about' element={<About />} />
<Route path='/contact' element={<Contact />} />
<Route path='/products' element={<Products/>} />
<Route path='/events' element={<Events/>} />
<Route path='*' element={<NotFound404/>}></Route>
</Routes>
</BrowserRouter>
此时,路由有一个问题,那就是点击<About>之后,回不去了,只能点击浏览器的回退按钮,要是页面始终展示导航条就好了。由于导航条在Home组件,也就是说Home组件始终要显示。About等组件的内容都是在点击之后才会显示,也就是先显示Home组件,再显示 About组件,只有先导航到home路由,才有机会导航到about的路由, home路由是about路由的父路由,在React Router 6中,Route可以嵌套,只要一个<Route>包含其它Route,它就是父路由,那么路由就可以这么写
<BrowserRouter>
<Routes>
<Route path='/' element={<Home />} >
<Route path='about' element={<About />} />
<Route path='contact' element={<Contact />} />
<Route path='products' element={<Products />} />
<Route path='events' element={<Events />} />
</Route>
<Route path='*' element={<NotFound404 />}></Route>
</Routes>
</BrowserRouter>
子路由前面的/可以去掉,React会自动组合(父路由/+子路由"about")。那子路由匹配成功后,要展示内容放到什么地方? 由于是子路由,肯定要先匹配父路由,只有父路由匹配成功了,才能匹配子路由。也就是只有父路由对应的组件展示出来了,才有机会展示子路由对应的内容,子路由的内容应该放到父路由的对应的组件里面,也就是About等组件要放到Home组件里面 。那具体怎么写呢?React Router 提供了<Outlet>组件,只要子路由匹配成功,<Outlet />组件可以动态成渲染子路由定义的组件内容。<Outlet />放到Home组件中,具体放到哪里,就看业务需要,比如与导航条并列
import { Link, Outlet } from "react-router-dom"; // 首页内容
export const Home = () => (
<section className="home">
<h1>企业网站</h1>
<p>首页内容</p>
<nav>
{/* 添加了四个导航组件Link */}
<Link to='/about'>关于我们</Link>
<Link to='/events'>企业事件</Link>
<Link to='/products'>公司产品</Link>
<Link to='/contact'>联系我们</Link>
</nav>
<Outlet />
</section>
)
点击每一个<Link>, 和它匹配成功的子路由所定义组件,都会正确地渲染,并且是渲染在<Outlet> 位置,正确的组件替换掉了<Outlet>。
给整个组件添加点样式,pages.css 内容如下,并在index.js中引用
html, body, #root {
height: 100%;
}
h1 {
font-size: 3em;
color: slategray;
}
/* home 组件 */
.home {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.home > nav {
display: flex;
justify-content: space-around;
padding: 1em;
width: calc(100% - 2em);
border-top: dashed 0.5em ghostwhite;
border-bottom: dashed 0.5em ghostwhite;
background-color: slategray;
} .home > nav a {
font-size: 2em;
color: ghostwhite;
flex-basis: 200px;
} /* 其它组件 */
section.events,
section.products,
section.contact {
flex-grow: 1;
margin: 1em;
display: flex;
justify-content: center;
align-items: center;
}
/* 404页面 */
.whoops-404 {
position: fixed;
top: 0;
left: 0;
z-index: 99;
display: flex;
width: 100%;
height: 100%;
margin: 0;
justify-content: center;
align-items: center;
background-color: darkred;
color: ghostwhite;
font-size: 1.5em;
}
整个页面如下展示
这时也会发现一个问题,刚进入页面时,页面只展示了导航条, 没有显示实际的内容,只有点击某个导航后,才显示内容,这显然不合适,即使不点击,也要看到核心内容,比如公司产品。针对此种情况,React Router 提供了索引路由,
<Route path='/' element={<Home />} >
{/* 索引路由 */}
<Route index element={<Products/>} />
<Route path='about' element={<About />} />
<Route path='contact' element={<Contact />} />
<Route path='products' element={<Products />} />
<Route path='events' element={<Events />} />
</Route>
索引路由和父路由共享路径。当localhost:3000时,path='/'匹配成功,渲染<Home>组件,渲染过程中有<Outlet>组件,就要去匹配子路由,由于索引路由的路径也是父路由的路径,此时URL就是父路由的路径,匹配成功,显示Products组件,也可以把索引路由看作是默认的子路由,当没有其它子路由匹配的时候,就渲染它,总要显示一个子路由吗?稍微修饰一下公司产品模块,列出几个产品,比如手机,电脑等,
export const Products = () => (
<section className="products">
<Link to='/details/telphone'>手机</Link>
<Link to='/details/computer'>电脑</Link>
</section>
)
当点击某个产品,进入产品详情页面,在pages.js中加一个详情组件
// 产品详情组件
export const Details = () => {
return <p>详情内容</p>
}
问题是当跳转到产品详情页面时,它怎么知道是哪个产品呢?匹配的路由怎么写?路由的格式就是路径后面加上冒号 ,再加参数,比如details/:type,组件中可以使用useParams获取。为了展示,把路由写到根路由index.js下
<Route path='products' element={<Products />} />
<Route path='details/:type' element={<Details />} />
<Route path='events' element={<Events />} />
同时Details组组件改为
import { useParams } from "react-router-dom"; export const Details = () => {
let params = useParams();
return <p>产品: {params.type}</p>
}
还有 一个小问题,导航条能不能提示位于哪个导航上?React Router 提供了NavLink 组件, 它和Link功能是一样的,都是标识请求,只是使用的场景不一样。navLink 提供了一个isActive 属性,可以设置高亮样式。
<NavLink to='/about' className={({ isActive }) => isActive ? "selectedStyle" : ""}>关于我们</NavLink>
当选中之后,isActive是true。
除了这种声明式的路由,也可以使用编程式路由,核心就是useNavigate. 在Detail里面,添加button,跳转到Product。
import { useParams, useNavigate } from "react-router-dom";
export const Details = () => {
let params = useParams();
let navigate = useNavigate();
return (
<p>
产品: {params.type}
<br />
<button onClick={() => {
navigate("/products");
}}>后退</button>
</p>
);
}
React Router 主要概念
React Router三个主要作用:监听(订阅)和操作浏览器的历史记录,匹配URL到你配置的路由,从匹配的路由中渲染嵌套的UI(完整的UI)
location是基于浏览器内置的window.location对象,React Router 自己拥有的一个特殊的对象。它表示用户在哪里,几乎就是URL的对象表示,但包含的信息比URL多。Location State则是存在Location对象上的一个值,但它并没有在URL中显示。它存在浏览器的内存中,并不可见,样子像hash或搜索参数。当用户在网页中导航时,浏览器持续追踪每一个location,形成浏览器的历史记录。
history也是一个对象,它使React Router 可以订阅URL的变化,并提供API来操作浏览器的历史记录。
segment:URL和路径模式中两个/之间的部分,比如'/user/123', user和123都是segment。路径模式很像URL,但它包含特殊字符,比如 "/users/:userId"和 "/docs/*" ,一个包含:,一个包含*。路径模式下“:userId” 又称为动态segment,因为它能匹配任意值。URL params就是匹配动态segment成功的值。/users/123 匹配/users/:userId, 123就是url params。
Match也是一个对象,它包含了URL和路由匹配成功的信息,比发path和URL Params。Matches是和当前URL匹配成功的一组路由。
客户端路由时,开发者可以通过API,如history.pushState(),来操作浏览器的历史记录,而不会发送服务器请求,但它仅仅是改变了URL,并没有改变UI。我们需要做的是改变URL的同时,改变UI。问题是浏览器并没有提供一个方法来监听URL,从而订阅变化。为此,React Router创建了histroy 对象,来监听URL的变化。<Router>,就是<BrowserRouter>,它会创建history对象,并订阅浏览器历史记录的变化,当URL变化时,它会更新history对象的state,从而引起APP的重新渲染,正确的UI显示出来。history的state是一个location对象,如下所示
{
pathname: "/bbq/pig-pickins",
search: "?campaign=instagram",
hash: "#menu",
state: null,
key: "aefz24ie"
}
pathname,路由匹配的部分,路由进行匹配的时候,只匹配pathname。state,来自于history.pushState()。pushState()第一个参数就是state, 但它不会改变URL。React Router 进行抽象,把state放到了location对象中。改变location 的state有两种方式
<Link to="/pins/123" state={{ fromDashboard: true }} />; let navigate = useNavigate();
navigate("/users/123", { state: partialUser });
在跳转到的page,可以使用useLocation获取到
let location = useLocation();
location.state;
只要改变URL,就会改变location 对象,想要获取URL的信息,就使用location对象。当URL改变后,React Router就用location来匹配你配置的路由,然后把匹配成功的拿出来,进行渲染。配置的路由就是<Routes>和<Route>组件。
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
<Route path="new" element={<EditTeam />} />
</Route>
</Route>
<Route element={<PageLayout />}>
<Route path="/privacy" element={<Privacy />} />
</Route>
</Routes>
<Routes>会递归遍历它的children,也就是<Route>,把属性拿出来,形成一个对象。
let routes = [
{
element: <App />,
path: "/",
children: [
{
index: true,
element: <Home />
},
{
path: "teams",
element: <Teams />,
children: [
{
path: ":teamId",
element: <Team />
},
{
path: "new",
element: <NewTeamForm />
}
]
}
]
},
{
element: <PageLayout />,
children: [
{
element: <Privacy />,
path: "/privacy"
}
]
}
];
形成一个总的路由配置表
[
"/",
"/teams",
"/teams/:teamId",
"/teams/new",
"/privacy"
];
如果现在有一个URL是/teams/new,哪个路由会匹配它,有两个
/teams/new
/teams/:teamId
这时React Router 必须做决定,因为只能有一条路由匹配,React Router会对你路由进行排名,依据就是路由中的segment, 静态,动态,数量等等,找出最精准匹配的那一条路由。在这里,就是 /teams/new 这条路由。当一条路由成功匹配到URL后,它会以match对象的方式进行展示。匹配到<Route path=":teamId" element={<Team/>}/>这条路由,将会生成下面这个对象
{
pathname: "/teams/firebirds",
params: {
teamId: "firebirds"
},
route: {
element: <Team />,
path: ":teamId"
}
}
由于路由是树形结构,一个URL可能匹配到树的整个分支。/teams/firebirds
<Routes>
<Route path="/" element={<App />}>
<Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
</Route>
</Route>
</Routes>
React Router从这些路由和URL中,会创建一组匹配的路由,从而渲染出嵌套的UI来匹配嵌套的路由。
[
{
pathname: "/",
params: null,
route: {
element: <App />,
path: "/"
}
},
{
pathname: "/teams",
params: null,
route: {
element: <Teams />,
path: "teams"
}
},
{
pathname: "/teams/firebirds",
params: {
teamId: "firebirds"
},
route: {
element: <Team />,
path: ":teamId"
}
}
];
有了匹配的路由,渲染React Element树就简单了
<App>
<Teams>
<Team />
</Teams>
</App>
看一下"/privacy",它匹配的路由是
<Route path="/" element={<App />}>
<Route element={<PageLayout />}>
<Route path="/privacy" element={<Privacy />} />
</Route>
</Route>
要渲染的React Element树
<App>
<PageLayout>
<Privacy />
</PageLayout>
</App>
PageLayout路由有点奇怪,没有path属性,只有Element属性,称为布局路由,仅仅用来做布局的。
V5 和V6的不同
1,React Router v6 使用了大量的React Hooks,因此升级V6前,要先升级React到16.8以上。
2,使用<Routes> 代替<Switch>,<Routes>使用的是最佳匹配路由算法,并且路由能嵌套
3,组件内部有<Link>和<Route>时,<Link>的to属性和Route>的path属性,不用再手动构建,而是直接写
<Route path={`${match.path}/:id`}> -> <Route path=":id" element={<UserProfile />} />
此时<Route path>和<Link to> 是相对路由和Link,它们自动构建在父路由path和URL上。对应的,当路由有后代路由,且这些路由是定义在其它组件中,路由的path要用*,表示深度匹配。
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="users/*" element={<Users />} />
</Routes>
</BrowserRouter>
);
} function Users() {
return (
<div>
<nav>
<Link to="me">My Profile</Link>
</nav> <Routes>
<Route path=":id" element={<UserProfile />} />
<Route path="me" element={<OwnUserProfile />} />
</Routes>
</div>
);
}
4,<Route>使用element,而不是compoent, render属性,element的取值也是React Element,好处就是可以像普通的React Element传递属性
// animate 是自定义属性
<Route path=":userId" element={<Profile animate={true} />} />
// 组件使用
function Profile({ animate }) {
let params = useParams();
let location = useLocation();
}
5, <Route> 的path属性不再接受正则表达式,/users/:id? 无效了。
6,react-router-config包里的功能都集成到V6中,使有useRoute,而不是react-router-config
function App() {
let element = useRoutes([
// These are the same as the props you provide to <Route>
{ path: "/", element: <Home /> },
{
path: "invoices",
element: <Invoices />,
// Nested routes use a children property, which is also
// the same as <Route>
children: [
{ path: ":id", element: <Invoice /> },
{ path: "sent", element: <SentInvoices /> }
]
},
// Not found routes work as you'd expect
{ path: "*", element: <NotFound /> }
]); // The returned element will render the entire element
// hierarchy with all the appropriate context it needs
return element;
}
当进行服务端渲染时,要用matchRoutes
7,useNavigate 代替了useHistory , Navigate 组件代替了Redirect 组件
import { Navigate } from "react-router-dom"; function App() {
return <Navigate to="/home" replace state={state} />;
}
8, <Link>没有了component属性,只能渲染成标签。
9,<NavLink/> 去掉了activeClassName 和 activeStyle, 要使用isActive
10,StaticRouter 称动了react-router-dom/server.
import { StaticRouter } from "react-router-dom/server";
React Router 6的更多相关文章
- [Redux] Filtering Redux State with React Router Params
We will learn how adding React Router shifts the balance of responsibilities, and how the components ...
- [转] React Router 使用教程
PS:react-route就是一个决定生成什么父子关系的组件,一般和layout结合起来,保证layout不行,内部的子html进行跳转 你会发现,它不是一个库,也不是一个框架,而是一个庞大的体系. ...
- [Redux] Navigating with React Router <Link>
We will learn how to change the address bar using a component from React Router. In Root.js: We need ...
- [Redux] Adding React Router to the Project
We will learn how to add React Router to a Redux project and make it render our root component. Inst ...
- React Router基础使用
React是个技术栈,单单使用React很难构建复杂的Web应用程序,很多情况下我们需要引入其他相关的技术 React Router是React的路由库,保持相关页面部件与URL间的同步 下面就来简单 ...
- 最新的chart 聊天功能( webpack2 + react + router + redux + scss + nodejs + express + mysql + es6/7)
请表明转载链接: 我是一个喜欢捣腾的人,没事总喜欢学点新东西,可能现在用不到,但是不保证下一刻用不到. 我一直从事的是依赖angular.js 的web开发,但是我怎么能一直用它呢?看看最近火的一塌糊 ...
- react router 4.0以上的路由应用
thead>tr>th{padding:8px;line-height:1.4285714;border-top:1px solid #ddd}.table>thead>tr& ...
- React Router 使用教程
一.基本用法 React Router 安装命令如下. $ npm install -S react-router 使用时,路由器Router就是React的一个组件. import { Router ...
- 关于react router 4 的小实践
详细代码栗子:https://github.com/wayaha/react-dom-CY clone然后 npm install npm start 分割线 1.这个项目使用create-react ...
- React Router 4.x 开发,这些雷区我们都帮你踩过了
前言 在前端框架层出不穷的今天,React 以其虚拟 DOM .组件化开发思想等特性迅速占据了主流位置,成为前端开发工程师热衷的 Javascript 库.作为 React 体系中的重要组成部分:Re ...
随机推荐
- Ubuntu 启用交换分区
前言 交换分区也称之为 swap 分区,允许系统在内存不足的情况下将内存程序写入文件,防止系统卡死失去响应的情况发生. 检查现有交换分区 首先,确认系统中是否已存在交换分区或文件.在终端中输入以下命令 ...
- 5GC 关键技术之网络切片
目录 文章目录 目录 前文列表 网络切片的需求来自于业务对网络提出的差异化要求 基于 3 大业务场景的切片 基于切片资源访问对象的切片 网络切片的商业价值 网络切片的底层技术支撑 网络切片的粒度 网络 ...
- Doug Lea大师的佳作CopyOnWriteArrayList,用不好能坑死你!
一.写在开头 我们在学习集合或者说容器的时候了解到,很多集合并非线程安全的,在并发场景下,为了保障数据的安全性,诞生了并发容器,广为人知的有ConcurrentHashMap.ConcurrentLi ...
- 使用docker 5分钟搭建一个博客(mysql+WordPress)
目录 一.系统环境 二.前言 三.搭建博客 3.1 创建wordpress和mysql容器 3.2 在wordpress界面设置个人博客信息 3.3 WordPress容器创建命令的简化版本 一.系统 ...
- golang import 导入的四种方式
1 标准导入: import "package_name" 2 导入别名: import ( alias "package_name" ) 3 匿名导入: _ ...
- GitHub two-factor authentication开启教程
问题描述 最近登录GitHub个人页面动不动就有一个提示框"...... two-factor authentication will be required for your accoun ...
- 分布式定理--CAP定理
cap定理指的是,在一个分布式系统中,只能满足cap中的两项. C consistency 一致性 A availability 可用性 P partition tolerance 分区可容错性 -- ...
- IDEA 新建 MAVEN JAVA 控制台程序 Console
IDEA 新建 MAVEN JAVA 控制台程序 Console File - New - Project-左侧选择 Maven,右侧勾选"Create from archetype&quo ...
- docker on windows v19 红色启动不了
遇到: error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.40/containers/json: open //./ ...
- element table根据条件隐藏复选框
在<el-table>标签加 :cell-class-name="cellClass" 在 <el-table-column type="selecti ...