参考官方文档:react-router官方文档
ReactRouter 是一种前端路由的实现方式,关于前端路由的原理后续补充
Router 的新版本号是 v5,ReactRouter 包含 3 个库:react-router、react-router-dom 和 react-router-native。
react-router 提供最基本的路由功能,实际使用时,我们不会直接安装 react-router,而是根据应用运行的环境选择安装 react-router-dom(在浏览器中使用)或 react-router-native(在 react-native 中使用)。react-router-dom 和 react-router-native 都依赖于 react-router,所以在安装时,react-router 也会自动安装。
注:react-router 依旧遵循一切皆组件的思想
在浏览器客户端中使用
1. 安装
yarn add react-router-dom
注:React Router v4+ 是对 React Router 的一次彻底重构,采用动态路由,遵循 React 中一切皆组件的思想,每一个 Route(路由)都是一个普通的 React 组件,所以 v4+ 版本不兼容之前的 ReactRouter 版本
2. 路由器
ReactRouter 通过 Router 和 Route 两个组件完成路由功能
所有的路由配置组件 Route 都定义为 Router 的子组件
在 Web 应用中,一般会使用对 Router 进行包装过的 BrowserRouter 或 HashRouter 两个组件
BrowserRouter 使用 HTML 5 的 history API(pushState、replaceState 等)实现应用的 UI 和 URL 的同步。
HashRouter 使用 URL 的 hash 实现应用的 UI 和 URL 的同步。
2.1 Router组件
Router 会创建一个 history 对象,history 用来跟踪 URL,当 URL 发生变化时,Router 的后代组件会重新渲染。
ReactRouter 中提供的其他组件可以通过 context 获取 history 对象,这也隐含说明了 ReactRouter 中的其他组件必须作为 Router 组件的后代组件使用。但 Router 中只能有唯一的一个子元素,这个组件可以是自定义的组件,或者第html 标签
// 正确
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>), document.getElementById('root'))
// 错误,Router 中包含两个子元素
ReactDOM.render((
<BrowserRouter>
<App1 />
<App2 />
</BrowserRouter> ),document.getElementById('root'))
3. 路由配置
Route 是使用频率最高的组件,每当有一个组件需要根据 URL 决定是否渲染时,就需要创建一个 Route,Route组件中几个常用属性:path 和 component 和 render,children,exact,同时, component 和 render, children属性 也是 Route渲染组件的方式,exact是路径的完全匹配和部分匹配
3.1 path 属性
用来配置路由跳转的路径
当 URL 匹配一个 Route 时,这个 Route 中定义的组件就会被渲染出来;
反之,Route 不进行渲染(Route 使用 children 属性渲染组件除外)
当使用 BrowserRouter 时,path 用来描述这个 Route 匹配的 URL 的 路径名字;
当使用 HashRouter 时,path 用来描述这个 Route 匹配的 URL 的 hash
3.2 component属性
用于渲染匹配上路径的组件
component 的值是一个组件,当 URL 和 Route 匹配时,component 属性定义的组件就会被渲染
<Route path='/foo' component={Foo}>
当URL="http://example.com/foo"时,Foo组件会被渲染
3.3 render属性
render的值是一个函数,可以传入一个props参数,这个函数返回一个React元素,它可以方便地为待渲染的组件传递额外的属性,同时它也可以为当前渲染的组件中再嵌套路由,也就是常说的二级,和三级 路由等
<Route path='/foo' render={(props) =>( <Foo {...props} data={extraProps} /> )}>
Foo 组件接收了一个额外的data属性
3.4 children属性
children的值也是一个函数,可以传入一个props参数,函数返回要渲染的React元素。与前两种方式不同之处是,无论是否匹配成功,children返回的组件都会被渲染
<Route path='/foo' children={(props) => ( <div> <Foo /> </div> </Route>
3.5 match属性
当URL和Route匹配时,Route会创建一个match对象作为props中的一个属性传递给被渲染的组件,同时这个对象包含以下4个属性
(1)params:Route的path可以包含参数,例如<Routepath='/foo/:id'>包含一个参数id。params就是用于从匹配的URL中 解析出path中的参数,例如,当URL="http://example.com/foo/1"时,params ={id: 1}。
(2)isExact:是一个布尔值,当URL完全匹配时,值为true;当URL部分匹配时,值为false。例如,当path="/foo"、 URL="http://example.com/foo"时,是完全匹配;当URL="http://example.com/ foo/1"时,是部分匹配。
(3)path:Route的path属性,构建嵌套路由时会使用到。
(4)url:URL的匹配部分
演示上边的属性用法,并将所有的路由抽离到一个路由文件中,进行统一管理所有的路由
1. 脚手架创建项目后的目录
安装路由
yarn add react-router-dom
App.js
import React from "react";
function App() {
return (
<div className="App">
<header className="App-header">我是App组件</header>
</div>
);
}
export default App;
router.js(此文件为所有的路由配置文件,所有路由为同步加载)
import React from "react";
import { Route, BrowserRouter } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
const AppRouter = () => (
<BrowserRouter>
<Route component={App} path="/" />
<Route component={SheQu} path="/sq" />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
</BrowserRouter>
);
export default AppRouter;
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppRouter from "./router";
ReactDOM.render(<AppRouter />, document.getElementById("root"));
compoent文件夹的文件内容都一样
Mine.js, News.js, SheQu.js
import React, { Component } from "react";
export class Mine extends Component {
render() {
return <div>个人中心</div>;
}
}
export default Mine;
启动项目
yarn start
可以看到,页面中已经显示出渲染的路由,默认渲染“/” , 即默认路径
再地址栏上手动修改路径,页面会进行改变,比如切换到 “/mine” 路径
然而问题来了, 不管你怎么切换后几个路径,App组件的内容都会渲染,这就需要使用Switch组件 和 Route 中的 exact 属性了
当URL和多个Route匹配时,这些Route都会执行渲染操作。如果只想让第一个匹配的Route渲染,那么可以把这些Route包到一个Switch组件中。如果想让URL和Route完全匹配时,Route才渲染,那么可以使用Route的exact属性,Switch和exact常常联合使用
修改router.js, 使用Switch 组件 和 exact
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
const AppRouter = () => (
<BrowserRouter>
<Switch>
<Route component={App} path="/" exact />
<Route component={SheQu} path="/sq" />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
</Switch>
</BrowserRouter>
);
export default AppRouter;
此时再进行手动切换路径,就正常了
使用render函数 或者children函数 代替 component属性,下边这种写法依旧可以渲染
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
const AppRouter = () => (
<BrowserRouter>
<Switch>
{/* <Route children={() => <App />} path="/" exact /> */}
<Route render={() => <App />} path="/" exact />
<Route component={SheQu} path="/sq" />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
</Switch>
</BrowserRouter>
);
export default AppRouter;
4. 路由跳转
Link 和 NavLink 是React Router提供的链接组件,一个Link组件定义了当点击该Link时,页面应该如何路由
NavLink常用属性
activeClassName : 默认选中的路由的 class 名字
activeStyle: 默认选中的路由的样式,这是一个对象
to: 跳转到的路径
Link 属性:
to:string || Object 跳转的路径
修改App.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
export class App extends Component {
render() {
return (
<div>
<ul>
<li>
<Link to="/news">新闻</Link>
</li>
<li>
<Link to="/mine">我的</Link>
</li>
<li>
<Link to="/sq">社区</Link>
</li>
</ul>
{this.props.children}
</div>
);
}
}
export default App;
修改router.js
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route component={SheQu} path="/sq" exact />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
刷新页面,点击新闻,已经可以跳转了
使用 Redirect组件 进行重定向 默认 选中 新闻路由
import React from "react";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route component={SheQu} path="/sq" exact />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
<Redirect to="/news" />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
将地址栏的后边的路由清空,重新刷新,默认选中了 新闻 对应的组件
方法跳转
history.push() 以及 history.replace() 方法
修改App.js, 由于App 组件 并不是被当作路由组件,它只是一个包裹嵌套路由的容器,所以在这个组件中 没有 路由 的 history 对象,这就需要使用 withRouter 这个高阶组件把 组件 与 路由 绑定进来, 打印this.props 就可以拿到history 对象了,
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
export class App extends Component {
render() {
console.log(this.props);
const { history, children } = this.props;
return (
<div>
<ul>
<li
onClick={() => {
history.push("/news");
}}
>
新闻
</li>
<li
onClick={() => {
history.push("/mine");
}}
>
我的
</li>
<li
onClick={() => {
history.push("/sq");
}}
>
社区
</li>
</ul>
{children}
</div>
);
}
}
export default withRouter(App);
清空, 刷新后,点击,路由继续跳转了。
5. 关于 404 页面 , 直接添加一个组件,不带任何路径就行 <Route component={NoMatch} />
import React from "react";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Mine from "./components/Mine";
import News from "./components/News";
import NoMatch from "./components/AppChild";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route render={() => <SheQu />} path="/sq" exact />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
<Route component={NoMatch} />
<Redirect to="/news" />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
6. 路由传参
App.js
history.push("/news?id=10");
import React, { Component } from "react";
import { withRouter, Link } from "react-router-dom";
export class App extends Component {
render() {
const { history, children } = this.props;
return (
<div>
<ul>
<li
onClick={() => {
history.push("/news?id=10");
}}
>
新闻
</li>
<li>
<Link to="/mine/10"> 我的</Link>
</li>
<li
onClick={() => {
history.push("/sq");
}}
>
社区
</li>
</ul>
{children}
</div>
);
}
}
export default withRouter(App);
News.js
import React, { Component } from "react";
export class News extends Component {
render() {
console.log(this.props);
return <div>我是news 新闻</div>;
}
}
export default News;
再 location 中 获取参数,关于动态路由,看官方例子,以下不做赘述
整合路由的一些知识,来个项目实站练习
首页,新闻,社区,我的,404页面,新闻详情页面, 目录如下
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppRouter from "./router";
ReactDOM.render(<AppRouter />, document.getElementById("root"));
App.js
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import "./css/app.css";
export class App extends Component {
constructor(props) {
super(props);
this.state = {
navList: [
{
id: 1,
name: "首页",
path: "/"
},
{
id: 2,
name: "新闻",
path: "/news"
},
{
id: 3,
name: "社区",
path: "/sq"
},
{
id: 4,
name: "我的",
path: "/mine"
}
]
};
}
render() {
const { children } = this.props;
const { navList } = this.state;
return (
<div className="app">
<ul>
{navList.map(item => (
<li
key={item.id}
onClick={() => this.props.history.push(item.path)}
>
{item.name}
</li>
))}
</ul>
<div>{children}</div>
</div>
);
}
}
export default withRouter(App);
app.css
body {
margin: 0;
padding: 0;
}
.app ul {
display: flex;
margin: 0;
background-color: #ccc;
}
.app ul li {
list-style: none;
padding: 10px;
cursor: pointer;
}
.News {
display: flex;
}
.news {
display: block !important;
background-color: #fff !important;
border: 1px solid #666;
width: 400px;
margin: 10px !important;
}
.newsInfor {
width: 500px;
background-color: #eee;
height: 300px;
}
components文件夹下
Home.js, MIne.js, SheQu.js, 内容自己改改啊,都一样
export class Home extends Component {
render() {
return <div>我是首页</div>;
}
}
export default Home;
router.js
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Home from "./components/Home";
import Mine from "./components/Mine";
import News from "./components/News";
import NoMatch from "./components/NoMatch";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route component={Home} path="/" exact />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
<Route component={SheQu} path="/sq" />
<Route component={NoMatch} />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
News.js
import React, { Component } from "react";
export class News extends Component {
constructor(props) {
super(props);
this.state = {
newsList: [
{
id: 1,
title: "hello , im in the world1 ?"
},
{
id: 2,
title: "hello , im in the world2 , you are OK ?"
},
{
id: 3,
title: "hello , im in the world3, China is Good ? "
},
{
id: 4,
title: "hello , im in the world4 ,english is bad?"
}
]
};
}
itemHandle(id) {
}
render() {
return (
<div className="News">
<ul className="news">
{this.state.newsList.map(item => (
<li key={item.id} onClick={() => this.itemHandle(item.id)}>
{item.title}
</li>
))}
</ul>
<div className="newsInfor">{this.props.children}</div>
</div>
);
}
}
export default News;
启动服务,点击新闻,首页,等切换一下
点击新闻列表,展示新闻详情
添加newsInfor.js
import React, { Component } from "react";
export class newsInfor extends Component {
constructor(props) {
super(props);
this.state = {
newsInforList: [
{
id: 1,
infor: "我是第一个hello world"
},
{
id: 2,
infor: "我是第二个hello world"
},
{
id: 3,
infor: "我是第三个hello world"
},
{
id: 4,
infor: "我是第四个hello world"
}
]
};
}
filterNews() {
let id = this.props.match.params.id;
const { newsInforList } = this.state;
return newsInforList.filter(item => {
return item.id === Number(id);
});
}
render() {
return (
<div>
{this.filterNews().map(item => (
<p key={item.id}>{item.infor}</p>
))}
</div>
);
}
}
export default newsInfor;
修改router.js
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Home from "./components/Home";
import Mine from "./components/Mine";
import News from "./components/News";
import NoMatch from "./components/NoMatch";
import newsInfor from "./components/newsInfor";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route component={Home} path="/" exact />
<Route component={Mine} path="/mine" />
<Route component={News} path="/news" />
<Route component={newsInfor} path="/newsInfor/:id" />
<Route component={SheQu} path="/sq" />
<Route component={NoMatch} />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
News.js 中补全 事件回调函数
import React, { Component } from "react";
export class News extends Component {
constructor(props) {
super(props);
this.state = {
newsList: [
{
id: 1,
title: "hello , im in the world1 ?"
},
{
id: 2,
title: "hello , im in the world2 , you are OK ?"
},
{
id: 3,
title: "hello , im in the world3, China is Good ? "
},
{
id: 4,
title: "hello , im in the world4 ,english is bad?"
}
]
};
}
itemHandle(id) {
this.props.history.push(`/newsInfor/` + id);
}
render() {
return (
<div className="News">
<ul className="news">
{this.state.newsList.map(item => (
<li key={item.id} onClick={() => this.itemHandle(item.id)}>
{item.title}
</li>
))}
</ul>
<div className="newsInfor">{this.props.children}</div>
</div>
);
}
}
export default News;
点击跳转, 可以看到拿到新闻详情数据了
进行路由嵌套
修改router.js
import React from "react";
import { Route, BrowserRouter, Switch } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Home from "./components/Home";
import Mine from "./components/Mine";
import News from "./components/News";
import NoMatch from "./components/NoMatch";
import newsInfor from "./components/newsInfor";
const AppRouter = () => (
<BrowserRouter>
<App>
<Switch>
<Route component={Home} path="/" exact />
<Route component={Mine} path="/mine" />
<Route
render={props => (
<News {...props}>
<Route path="/news/newsInfor/:id" component={newsInfor} />
</News>
)}
path="/news"
/>
<Route component={SheQu} path="/sq" />
<Route component={NoMatch} />
</Switch>
</App>
</BrowserRouter>
);
export default AppRouter;
修改News.js
import React, { Component } from "react";
export class News extends Component {
constructor(props) {
super(props);
this.state = {
newsList: [
{
id: 1,
title: "hello , im in the world1 ?"
},
{
id: 2,
title: "hello , im in the world2 , you are OK ?"
},
{
id: 3,
title: "hello , im in the world3, China is Good ? "
},
{
id: 4,
title: "hello , im in the world4 ,english is bad?"
}
]
};
}
itemHandle(id) {
this.props.history.push(`/news/newsInfor/` + id);
}
componentDidMount() {
this.props.history.push(`/news/newsInfor/` + this.state.newsList[0].id);
}
render() {
return (
<div className="News">
<ul className="news">
{this.state.newsList.map(item => (
<li key={item.id} onClick={() => this.itemHandle(item.id)}>
{item.title}
</li>
))}
</ul>
<div className="newsInfor">{this.props.children}</div>
</div>
);
}
}
export default News;
实战2 , 处理是否登录了,来决定路由的展示
Login.js
import React, { Component } from "react";
export class Login extends Component {
render() {
return (
<div className="login">
<input type="text" />
<br />
<input type="password" />
<br />
<button onClick={this.login}>登录</button>
</div>
);
}
login = () => {
// 存储登录标志
localStorage.setItem("isLogin", true);
this.props.history.push("/app");
};
}
export default Login;
router.js
当然你也可以再路由这里通过三木运算,判断,如果等过了,直接进来,如果没有,重定向到登录页面
import React from "react";
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
// 引入组件
import App from "./App";
import SheQu from "./components/SheQu";
import Home from "./components/Home";
import Mine from "./components/Mine";
import News from "./components/News";
import NoMatch from "./components/NoMatch";
import newsInfor from "./components/newsInfor";
import Login from "./components/Login";
const AppRouter = () => (
<BrowserRouter>
<Switch>
<Route component={Login} path="/" exact />
<Route
path="/app"
render={props => (
<App {...props}>
<Switch>
<Route component={Home} path="/app/home" />
<Route component={Mine} path="/app/mine" />
<Route
render={props => (
<News {...props}>
<Route
path="/app/news/newsInfor/:id"
component={newsInfor}
/>
</News>
)}
path="/app/news"
/>
<Route component={SheQu} path="/app/sq" />
<Route component={NoMatch} />
</Switch>
</App>
)}
/>
</Switch>
</BrowserRouter>
);
export default AppRouter;
News.js
import React, { Component } from "react";
export class News extends Component {
constructor(props) {
super(props);
this.state = {
newsList: [
{
id: 1,
title: "hello , im in the world1 ?"
},
{
id: 2,
title: "hello , im in the world2 , you are OK ?"
},
{
id: 3,
title: "hello , im in the world3, China is Good ? "
},
{
id: 4,
title: "hello , im in the world4 ,english is bad?"
}
]
};
}
itemHandle(id) {
this.props.history.push(`/app/news/newsInfor/` + id);
}
render() {
return (
<div className="News">
<ul className="news">
{this.state.newsList.map(item => (
<li key={item.id} onClick={() => this.itemHandle(item.id)}>
{item.title}
</li>
))}
</ul>
<div className="newsInfor">{this.props.children}</div>
</div>
);
}
}
export default News;
App.js
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
import "./css/app.css";
export class App extends Component {
constructor(props) {
super(props);
this.state = {
navList: [
{
id: 1,
name: "首页",
path: "/app/home"
},
{
id: 2,
name: "新闻",
path: "/app/news"
},
{
id: 3,
name: "社区",
path: "/app/sq"
},
{
id: 4,
name: "我的",
path: "/app/mine"
}
]
};
}
componentDidMount() {
// 获取是否登录的标志
const { history } = this.props;
let islogin = localStorage.getItem("isLogin");
if (islogin) {
history.push("/app/home");
} else {
history.push("/");
}
}
render() {
const { children } = this.props;
const { navList } = this.state;
return (
<div className="app">
<ul>
{navList.map(item => (
<li
key={item.id}
onClick={() => this.props.history.push(item.path)}
>
{item.name}
</li>
))}
</ul>
<div>{children}</div>
</div>
);
}
}
export default withRouter(App);
app.css
body {
margin: 0;
padding: 0;
}
.app ul {
display: flex;
margin: 0;
background-color: #ccc;
}
.app ul li {
list-style: none;
padding: 10px;
cursor: pointer;
}
.News {
display: flex;
}
.news {
display: block !important;
background-color: #fff !important;
border: 1px solid #666;
width: 400px;
margin: 10px !important;
}
.newsInfor {
width: 500px;
background-color: #eee;
height: 300px;
}
.login {
margin: 300px;
}
点击刷新跳转吧
总结: 当配合react 路由 进行开发时, 你可以先画一个草图,自己过一下流程图, 然后再设计路由,这样会很好理解,思路清晰一些,每个人路由设计的不一样,具体写法也不一样