一、简介
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配置我们应用的路由呢?先看下面官方文档简单示例
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原理
1. 整体思路
什么是前端路由?相对于后端路由,当一个应用切换页面时候,需要重新向服务器发起请求获取新的页面再渲染,前端路由切换页面不需要重新请求整个页面(可能需要请求异步的组件代码),这样不需要请求新页面,加载速度更快,并且不同路由之间可以共享状态,这能够提供更好的用户体验。
前端路由的实现有两个关键点:
- 改变url,而不触发页面刷新
- 监听url改变,重新渲染对应url的界面
React-Router和Vue-Router等路由库的原理大致类似,都支持了
前端路由的两种模式:history和hash,这两种模式都是利用了浏览器提供的API,实现了上面的两点:无刷新修改url和监听url改变。
2. 两种路由模式的原理
有两种模式的路由:history和hash,它们利用浏览器提供的``API。
下面看这两类API是如何支持无刷新修改URL和监听URL改变的:
history
history API:https://developer.mozilla.org/en-US/docs/Web/API/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回调。
3. React Router组件渲染原理:
React-Router使用context将路由信息传递给子组件,Router作为Provider负责更新路由信息并将信息传递,Route和Routes则根据context的改变,获取当前的路由信息,再根据自己的path、exact等属性判断是否需要渲染,然后再根据component、children、render属性渲染指定的组件。
HashRouter和BrowserRouter都是继承自Router,它们都是Provider,向Consumer提供路由信息,只是修改URL和监听URL变化的方式不同。
4. 两种模式的优缺点
- history模式的路由没有"#"字符,而是在域名后直接写路径,更加优雅
- 由于#后面的字符不会发给服务器,因此hash路由SEO比较差
- history需要服务器在访问不同的路径时候都能fallback到index.html,因此相对麻烦
网友评论