React-Router v5.x
一、简介
React 项目中路由管理主要使用 react-router 工具。
react-router 是个多包管理的项目,包括
- react-router // 核心模块
- react-router-dom // 浏览器环境的路由
- react-router-native // react-native 路由
- react-router-config // 静态路由配置
从 React-Router v5 开始,react-router 项目进行了拆分,通过 lerna 进行多包管理,react-router 为核心 React 路由功能,react-router-dom 基于 react-router,提供浏览器的路由功能,react-router-native 基于 react-router,提供 react-native 的路由功能。
所以前端使用 react-router5 以上的版本,安装 react-router-dom 就可以
npm install react-router-dom二、基本配置
react-router 一般是基于组件来控制路由(主要的组件有 BrowserRouter、HashRouter、Route、Link、Switch、Redirect 等),当然也可以使用 react-router-config 来支持类似 vue 的集中配置式路由。
注意,使用时候 react-router-config 时候要注意 react-router 版本,比如使用 react-router-config@5 和 react-router@6 就会报错。需要删掉 6.x 的 react-router(npm uninstall react-router 删掉 node_modules 内容,删掉 package.json 中 react-router 的 dependencies,删掉 package-lock.json),然后重装 5.x 的 react-router。
如何使用 react-router 配置我们应用的路由呢?先看下面官方文档简单示例
import React from 'react';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}这里示例中实现了 3 个导航按钮,并设置了不同路由对应的组件渲染。
其中BrowserRouter用来监听和解析 url,并将 history 等相关属性注入到组件中,Route组件负责接收 history 等路由相关属性并决定是否需要渲染 Route 中的组件。Route 根据传给它的 path 属性来匹配 url。
在用 Route 组件渲染的组件中,可以获取到路由相关属性,通过 withRouter 注入到组件的 props 中,对于函数式组件,也可以使用相关的 hook 来获取路由相关属性(useHistory, useRouteMatch,useLocation 等)。
react-router 提供的路由相关属性包括 history、location、match 等。history 主要用来进行路由切换(跳转、回退等)操作,location 保存了当前 url 的信息,match 保存了当前匹配的信息,注意 match 的信息是组件上层最近一级的 Route(因为 Route 可以嵌套,所以有层级的概念,关于嵌套路由后面有介绍)的匹配信息。
Switch 组件用在只渲染匹配到的第一个 Route 和 Redirect 组件的场景。
三、重定向
Redirect 用来做重定向,渲染这个组件将会重定向到 to 属性指定的路由,to 可以是个字符串,也可以是个对象。
<Redirect to="/somewhere/else" />
<Redirect
to={{
pathname: "/login",
search: "?utm=your+face",
state: { referrer: currentLocation }
}}
/>四、导航
Link 组件是导航组件,其中 to 属性描述要跳转到的地址,它可以是一个字符串或者对象,也可以是一个函数。
<Link to="/about">About</Link>
<Link to="/courses?sort=name" />
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
/>
<Link to={location => ({ ...location, pathname: "/courses" })} />
<Link to={location => `${location.pathname}?sort=name`} />注意
Link 导航、Redirect 重定向和编程式导航都可以指定 state,state 可以在 location 属性中访问到。
除了使用导航链接组件 Link 进行导航,也可以使用编程式导航,通过 history 的 push、replace、goBack 方法进行导航,组件通过 withRouter 或者 useHistory 获取 history 对象。
五、嵌套路由
嵌套路由通过组件嵌套实现,看官方的示例代码
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
useRouteMatch
} from 'react-router-dom';
// Since routes are regular React components, they
// may be rendered anywhere in the app, including in
// child elements.
//
// This helps when it's time to code-split your app
// into multiple bundles because code-splitting a
// React Router app is the same as code-splitting
// any other React app.
export default function NestingExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics() {
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function Topic() {
// The <Route> that rendered this component has a
// path of `/topics/:topicId`. The `:topicId` portion
// of the URL indicates a placeholder that we can
// get from `useParams()`.
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}在示例中可以看到,NestingExample 组件中配置了 home 和 topics 两个路由,topics 路由对应的组件 Topics 中又使用 Route 组件配置了子路由,注意这里使用了 useRouteMatch hook,它返回当前匹配上的路由,这里就是"/topics"。
六、动态路由
动态路由匹配,在 Route 组件上 path 属性通过冒号语法可以定义动态路由,使用的时候可以通过 withRouter 将 history、match 和 location 注入到组件的 props 中,其中 match. params 属性就是匹配到的动态路由。另外函数式组件还可以通过 useParams 钩子来获取匹配到的动态路由。
hook 版:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams
} from 'react-router-dom';
// Params are placeholders in the URL that begin
// with a colon, like the `:id` param defined in
// the route in this example. A similar convention
// is used for matching dynamic segments in other
// popular web frameworks like Rails and Express.
export default function ParamsExample() {
return (
<Router>
<div>
<h2>Accounts</h2>
<ul>
<li>
<Link to="/netflix">Netflix</Link>
</li>
<li>
<Link to="/zillow-group">Zillow Group</Link>
</li>
<li>
<Link to="/yahoo">Yahoo</Link>
</li>
<li>
<Link to="/modus-create">Modus Create</Link>
</li>
</ul>
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
</div>
</Router>
);
}
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}withRouter 版:
import React from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
withRouter
} from 'react-router-dom';
class Child extends React.Component {
render() {
return (
<div>
<h3>ID: {this.props.match.params.id}</h3>
</div>
);
}
}
const WrappedChild = withRouter(Child);
export default function ParamsExample() {
return (
<Router>
<div>
<h2>Accounts</h2>
<ul>
<li>
<Link to="/netflix">Netflix</Link>
</li>
<li>
<Link to="/zillow-group">Zillow Group</Link>
</li>
<li>
<Link to="/yahoo">Yahoo</Link>
</li>
<li>
<Link to="/modus-create">Modus Create</Link>
</li>
</ul>
<Switch>
<Route path="/:id" children={<WrappedChild />} />
</Switch>
</div>
</Router>
);
}七、Route 组件属性详细说明
Route 组件的 exact 属性指定是否使用精确匹配,如果使用精确匹配,则当 location 和 path 完全一致时候才认为匹配上,如果不使用精确匹配,则匹配前缀。
除了在 Route 标签中放置组件,即 children 属性,还有 component 和 render 属性可以指定 Route 渲染的组件。
Route 的 component 属性用来指定 Route 所要渲染的组件,如果 Route 匹配上 location,则渲染该组件,render 属性的功能和 component 一样。
那么 component 和 render 有什么区别呢?
如果我们有对组件进行包装的需求,那么传给 Route 的就是一个方法,由于每次渲染组件时候,新的方法都会对应新的组件,因此在这种场景下,应该使用 render 属性。即如果传给 Route 的组件是一个方法,则应该使用 render 属性,否则使用 component。
八、React Router 原理
整体思路
什么是前端路由?相对于后端路由,当一个应用切换页面时候,需要重新向服务器发起请求获取新的页面再渲染,前端路由切换页面不需要重新请求整个页面(可能需要请求异步的组件代码),这样不需要请求新页面,加载速度更快,并且不同路由之间可以共享状态,这能够提供更好的用户体验。
前端路由的实现有两个关键点:
- 改变 url,而不触发页面刷新
- 监听 url 改变,重新渲染对应 url 的界面
React-Router 和 Vue-Router 等路由库的原理大致类似,都支持了
前端路由的两种模式:history 和 hash,这两种模式都是利用了浏览器提供的 API,实现了上面的两点:无刷新修改 url 和监听 url 改变。
两种路由模式的原理
有两种模式的路由:history 和 hash,它们利用浏览器提供的 API。
下面看这两类 API 是如何支持无刷新修改 URL 和监听 URL 改变的:
history
改变 url
history 提供了几个方法改变 url,这些方法都不会触发页面刷新:
history.back(); // 回退
history.forward(); // 前进
history.go(-1); // 跳转到history栈中的指定点
history.pushState(state, unused); // 在history栈中增加一个记录
history.pushState(state, unused, url); //在history栈中增加一个记录
history.replaceState(stateObj, unused); // 将当前的记录替换为新的记录
history.replaceState(stateObj, unused, url); // 将当前的记录替换为新的记录监听 url 变化
首先,利用 window.onpopstate 回调,这个回调监听回退、前进事件。
但是调用 history.pushState()或者 history.replaceState()不会触发 popstate 事件,那么如何监听到 url 的 push 和 replace 事件呢?可以封装 pushState 和 replaceState 方法,在调用 pushState 和 replaceState 时候通知订阅者。这样就能监听到 url 的改变了。react-router 使用的 history 库就是用这种方法。
hash
改变 url
location.href = 'XXX',这样修改 URL,“#”后面的改动不会触发页面刷新。
监听 url
window.onhashchange 回调,“#”后面的改动会触发 onhashchange 回调。
React Router 组件渲染原理
React-Router 使用 context 将路由信息传递给子组件,Router 作为 Provider 负责更新路由信息并将信息传递,Route 和 Routes 则根据 context 的改变,获取当前的路由信息,再根据自己的 path、exact 等属性判断是否需要渲染,然后再根据 component、children、render 属性渲染指定的组件。
HashRouter 和 BrowserRouter 都是继承自 Router,它们都是 Provider,向 Consumer 提供路由信息,只是修改 URL 和监听 URL 变化的方式不同。
两种模式的优缺点
- HTML5 模式的路由没有"#"字符,而是在域名后直接写路径,更加优雅
- 由于#后面的字符不会发给服务器,因此 hash 路由 SEO 比较差,且不会在服务器生成日志记录
- HTML5 需要服务器在访问不同的路径时候都能 fallback 到 index.html,因此相对麻烦