这是我参与更文挑战的第8天,活动详情查看: 更文挑战
什么是前端路由?
传统上,路由是一个后端的概念。但 url 发生变化的时候,后端根据变化返回对应的文件。
随着单页应用的出现,前端也需要处理路由:
- 我们不希望 url 的变化引起浏览器的刷新,只需要切换页面内容。
- 当再次访问页面时,可以根据 url 展示对应的内容
- 更有语义的组织资源
React Router
就可以帮你管理路由,它是一个基于 React 之上的强大路由库,它也是 React 全家桶的其中一员(React + Redux + React Router),它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。
React Router 如何实现路由?
路由的基础架构十分简单,主要分为三部分:【路由定义】、【Router】、【页面】。我们通过【路由定义】来映射 path 和组件的关系,然后经过【Router】的解析,在【页面】的组件容器上展示 path 对应内容。
React Router 特性
-
声明式路由定义
对比后端(express)用路由表配置路由,React Router 使用 React tag 定义路由,它可以写在任何位置。const App = () => { return ( <div> <Router> <nav> <Link to="/about">About</Link> </nav> <div> <Route path="/about" component={About}></Route> </div> </Router> </div> ); }; 复制代码
-
动态路由(Dynamic Routing)
与动态路由对应的是静态路由(Static Routing),静态路由的路由表是不变的,在项目开始运行时,我们就知道了所有的路由关系:var express = require("express"); var router = express.Router(); router.get("/", function (req, res) { res.send("Wiki home page"); }); router.get("/about", function (req, res) { res.send("About this wiki"); }); module.exports = router; 复制代码
但是 React Router 是在页面 render 时才会被解析。
<Route>
作为一个组件,没有被渲染时,是访问不到他的路由(/about
);所以,路由表是随着组件的渲染而变化增加的。<Route path="/about" component={About} /> 复制代码
三种路由的实现方式
React Router 提供了三种路由的实现方式:
-
URL 路由
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。只要浏览器支持,都可以使用 URL 路由(/home
,/about
)import { BrowserRouter } from "react-router-dom"; 复制代码
<BrowserRouter basename={optionalString} forceRefresh={optionalBool} getUserConfirmation={optionalFunc} keyLength={optionalNumber} > <App /> </BrowserRouter> 复制代码
-
hash 路由
即地址栏 URL 中的 # 符号(此 hash 不是密码学里的散列运算)–http://www.abc.com/#/hello
。hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。import { HashRouter } from "react-router-dom"; 复制代码
<HashRouter basename={optionalString} getUserConfirmation={optionalFunc} hashType={optionalString} > <App /> </HashRouter> 复制代码
-
内存路由
路由的历史记录保存在内存中,不反映到浏览器的地址栏,在测试和非浏览器环境(如 React Native)中很有用。import { MemoryRouter } from "react-router"; 复制代码
<MemoryRouter initialEntries={optionalArray} initialIndex={optionalNumber} getUserConfirmation={optionalFunc} keyLength={optionalNumber} > <App /> </MemoryRouter> 复制代码
安装
- npm
npm install react-router-dom --save
复制代码
- yarn
yarn add react-router-dom --dev
复制代码
1st Example: 基础的路由切换
import React from "react";
import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
const App = () => (
{/* Router ⬇️ */}
<Router>
{/* 路由切换 ⬇️ */}
<nav>
<ul>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/about">about</Link>
</li>
<li>
<Link to="/users">users</Link>
</li>
</ul>
</nav>
{/* 容器组件 ⬇️ */}
<Switch>
{/* 路由匹配列表 ⬇️ */}
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/users" component={Users}></Route>
</Switch>
</Router>
);
function Home() {
return <div>Home</div>;
}
function About() {
return <div>About</div>;
}
function Users() {
return <div>Users</div>;
}
export default App;
复制代码
基于路由,组织资源
-
实现业务逻辑的松耦合
我们把 业务相关/代码相关 的部分通过路由整合在一起。
-
易于扩展、重构和维护
因为路由的实现没有杂糅在业务代码中,而是用 React Router 抽象出来,这样业务的逻辑单元就变成了一个个页面,无论是扩展新页面或者重构维护,从路由的角度入手都显得更加清楚。
-
路由层面实现 Lazy Load
因为我们将路由定义在了统一的地方,所以从路由层面做 Lazy Load 会更方便,并且可以做到加载不同的组件,第一次 loading 的效率都很高。
核心 API 介绍
<Route>
<Route>
组件是 React Router 中最重要的组件。它的基本职能是在路径匹配时,显示对应组件。
<Router>
{
// exact: 是否精确匹配 path
}
<Route exact path="/" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Router>
复制代码
还有一点,<Route>
是没有排他性的,就是说他可以同时渲染两个匹配上的组件(多匹配):
<Router>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</Router>
// 匹配到路径 /user 时,
// 组件 UserInfo 和 组件 UserDetail 会同时展现。
复制代码
Props
-
path: URL 中的路径。
-
component: 当匹配到 URL 时,单个的组件会被渲染。它可以被父 route 组件的
this.props.children
获取。const routes = ( <Route component={App}> <Route path="groups" component={Groups} /> <Route path="users" component={Users} /> </Route> ); class App extends React.Component { render() { return ( <div> {/* 这会是 <Users> 或 <Groups> */} {this.props.children} </div> ); } } 复制代码
-
components: 可以定义一个或多个已命名的组件,以
{name:component}
的形式定义,可以被 父 route 组件的 this.props[name] 获取。const routes = ( <Route component={App}> <Route path="groups" components={{ main: Groups, sidebar: GroupsSidebar }} /> <Route path="users" components={{ main: Users, sidebar: UsersSidebar }}> <Route path="users/:userId" component={Profile} /> </Route> </Route> ); class App extends React.Component { render() { const { main, sidebar } = this.props; return ( <div> <div className="Main">{main}</div> <div className="Sidebar">{sidebar}</div> </div> ); } } 复制代码
<Switch>
只显示第一个匹配的路由(改变默认的多匹配行为)。
<Router>
<Route path="/:user">
<User />
</Route>
<Route>
<NoMatch />
</Route>
</Router>
// 匹配到路径 /1 时,
// 只有组件 User 会同时展现。
复制代码
<Link>
展现的效果和普通的链接(<a>
)一样,但不会触发浏览器的刷新,而是由 React Router 接管。
<Link to="/about">About</Link>
复制代码
Props
- to: 跳转链接的路径。
- replace: 为
true
时,会替换 history 中的当前路径,而不是新增。
<NavLink>
类似 <Link>
,但是会添加当前选中状态(className)
<NavLink to="/faq" activeClassName="selected">
FAQs
</NavLink>
复制代码
Props
-
activeClassName: 该路由激活时,添加的 class,默认为“active”
-
activeStyle: 该路由激活时,添加的 style 样式。
<NavLink to="/faq" activeStyle={{ fontWeight: "bold", color: "red", }} > FAQs </NavLink> 复制代码
<Prompt>
在用户离开页面时,提示用户是否离开当前页面(如表单填写一半)。
<Prompt when={formIsHalfFilledOut} message="Are you sure you want to leave?" />
复制代码
Props
- message: 提示用户的消息
- when:
<Prompt>
的渲染条件
<Redirect>
重定向当前页面,而不改变旧的 URL。例如登陆跳转
<Route exact path="/">
{loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
复制代码
Props
- to: 重定向的 URL
- form: 需要被重定向的 URL