Reac-Router 使用及其原理
# 基于
# HashRouter
全局绑定 hashchange
事件,当哈希路由发生改变时(如由 /index ==> /index#/a
),触发监听函数,然后再函数中处理组件的显示与切换。
<a href="#/a">/a</a>
<a href="#/b">/b</a>
<div id="root" style="border: 1px solid; padding: 10px 20px"></div>
<script>
const container = document.getElementById("root");
window.addEventListener("hashchange", (e) => {
container.innerHTML = `当前路径是:${window.location.hash.slice(1)}`;
});
</script>
2
3
4
5
6
7
8
9
10
获取 hash
路由值
window.location.hash // => #/user
# BrowserRouter
使用了浏览器提供的
History
接口来实现路由
下面是 History
的属性及方法:

使用浏览器提供的 window.onpopstate
监听函数每当路径从路径栈中弹出就执行,并从回调函数中得到 PopStateEvent
事件对象,从中可以拿到调用 pushState
函数时传递的 state
。可以通过此控制界面的切换显示。
由于浏览器没有提供与 window.onpopstate
相对应的 window.onpushstate
方法,导致只有出栈时才能对 dom
有所操作,因此需要一个入栈时的监听函数。所以只有重写 window.history.pushState
方法,每次需要向路由栈中入栈时就执行调用自写 window.history.pushState
并在其中调用自定义的 window.onpushstate
函数,用来操作 dom
显示组件等等。实现如下:
<button onclick="push('/a')">/a</button>
<button onclick="push('/b')">/b</button>
<div id="root" style="border: 1px solid;"></div>
<script>
const container = document.getElementById("root");
window.onpopstate = function (e) {
console.log(e);
container.innerHTML = `当前路径是:${e.state.to}`;
};
window.onpushstate = function (to) {
container.innerHTML = `当前路径是:${to}`;
};
window.push = function (to) {
window.onpushstate(to);
// window.history.pushState 的三个参数:
// state 新的状态对象;标题(废弃); to 跳转到的路径
window.history.pushState({ to }, null, to);
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
window.history.pushState(a,b,c)
方法一共有接收三个参数:
- 参数一:
state
一个对象,要跳转到的路由页面下保存的额外数据。 - 参数二:要跳转到的路径界面标题(已废弃)。
- 参数三:要跳转的路由地址。
# react-router-dom
由 react-router 实现, React-router-dom 是浏览器端的,
Hashrouter 就是拥有了 hashhistory 的 ReactRouter
...
# 使用
案例效果如下:
案例实现如下
import React from "react";
import ReactDOM from "react-dom";
// import { HashRouter as Router, Route, Link } from "react-router-dom";
// import { HashRouter as Router, Route, Link } from "./react-router-dom2.0";
import { BrowserRouter as Router, Route, Link } from "./react-router-dom2.0";
import User from "./components/user";
import Profile from "./components/proflie";
import Home from "./components/home";
import Login from "./components/login";
import Protected from "./components/protected";
import MenuLink from "./components/menulink";
import NavHead from "./components/navhead";
import "bootstrap/dist/css/bootstrap.css";
// {/*exact 精确匹配*/}
// HashRouter 路由容器 用到了context
ReactDOM.render(
<Router>
<>
<nav className="navbar navbar-inverse">
<div className="container-fluid">
<NavHead />
<ul className="nav navbar-nav">
<li>
<MenuLink activeClassName="active" to="/" exact>
首页
</MenuLink>
</li>
<li>
<MenuLink
activeClassName="active"
to={{ pathname: "/user", state: { title: "用户管理" } }}
>
用户管理
</MenuLink>
</li>
<li>
<MenuLink
activeClassName="active"
to={{ pathname: "/profile", state: { title: "个人中心" } }}
>
个人中心
</MenuLink>
</li>
<li>
<MenuLink activeClassName="active" to="/login">
登陆
</MenuLink>
</li>
</ul>
</div>
</nav>
<div className="container">
<div className="row">
<div className="col-md-12">
<Route path="/" component={Home} exact />
<Route path="/user" component={User} />
<Route path="/login" component={Login} />
<Protected path="/profile" component={Profile} />
</div>
</div>
</div>
</>
</Router>,
document.getElementById("root")
);
// /user? /user
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import React, { Component } from "react";
export default class Home extends Component {
render() {
return <div>home</div>;
}
}
2
3
4
5
6
7
import React, { Component } from "react";
import { login } from "../utils/local";
export default class Login extends Component {
state = {
user: { username: "", email: "" },
};
handleChange = (name, e) => {
this.setState({
user: {
...this.state.user,
[name]: e.target.value,
},
});
};
handleClick = (e) => {
e.preventDefault();
login(this.state.user);
console.log(this.props);
let state = this.props.location.state;
if (state) {
this.props.history.push(state.from);
} else {
this.props.history.push("/");
}
};
render() {
return (
<div>
<form>
<label htmlFor="username">用户名</label>
<input
name="username"
type="text"
onChange={(e) => {
this.handleChange("username", e);
}}
/>
<label>密码</label>
<label htmlFor="email"></label>
<input
type="password"
name="email"
onChange={(e) => {
this.handleChange("email", e);
}}
/>
<button type="submit" onClick={this.handleClick}>
登陆
</button>
</form>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, { Component } from "react";
// import { Redirect, Route, Link, Switch, NavLink } from "react-router-dom";
import { Redirect, Route, Link, Switch } from "../react-router-dom2.0";
import UserList from "./userlist";
import UserAdd from "./useradd";
import UserDetail from "./userdetail";
import MenuLink from "./menulink";
export default class User extends Component {
render() {
return (
<div className="row">
<div className="col-md-2">
<ul className="nav nav-sidebar">
<li>
<MenuLink activeClassName="active" to="/user/list">
用户列表
</MenuLink>
</li>
<li>
<MenuLink activeClassName="active" to="/user/add">
添加用户
</MenuLink>
</li>
</ul>
</div>
<div className="col-md-10">
<Switch>
<Route path="/user/list" component={UserList} />
<Route path="/user/add" component={UserAdd} />
<Route path="/user/userdetail/:id" component={UserDetail} />
<Redirect to="/user/list" />
</Switch>
</div>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { Component } from "react";
import { addUser } from "../utils/local";
import { Prompt } from "../react-router-dom2.0";
export default class UserAdd extends Component {
state = {
user: { username: "", email: "" },
blocking: false,
};
handleChange = (name, e) => {
this.setState({
user: {
...this.state.user,
[name]: e.target.value,
},
blocking: this.state.blocking || e.target.value.length > 0,
});
};
handleClick = (e) => {
e.preventDefault();
addUser(this.state.user);
this.props.history.push("/user/list");
};
render() {
return (
<div>
<form>
<Prompt
when={this.state.blocking}
message={(location) => `你确定要离开${location.pathname}吗?`}
/>
<label htmlFor="username">用户名</label>
<input
name="username"
type="text"
onChange={(e) => {
this.handleChange("username", e);
}}
/>
<label>邮箱</label>
<label htmlFor="email"></label>
<input
type="text"
name="email"
onChange={(e) => {
this.handleChange("email", e);
}}
/>
<button type="submit" onClick={this.handleClick}>
提交
</button>
</form>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React, { Component } from "react";
import { getUser } from "../utils/local";
export default class UserDetail extends Component {
state = {
user: {},
};
componentDidMount() {}
render() {
console.log(this.props);
let user = this.props.location.state || getUser(this.props.match.params.id);
return (
<div>
<h1 style={{ textAlign: "center" }}>用户详情</h1>
<ul>
<li
key="123"
style={{ display: "flex", justifyContent: "space-around" }}
>
<p>ID</p>
<p>用户名</p>
<p>邮箱</p>
</li>
<li
key="asd"
style={{ display: "flex", justifyContent: "space-around" }}
>
<p>{user.id}</p>
<p>{user.username}</p>
<p>{user.email}</p>
</li>
</ul>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import React, { Component } from "react";
// import { Link } from "react-router-dom";
import { Link } from "../react-router-dom2.0";
import { getUserList } from "../utils/local";
export default class UserList extends Component {
state = {
users: [],
};
componentDidMount() {
let userList = getUserList();
this.setState({
users: userList,
});
}
render() {
return (
<div>
<ul style={{ listStyle: "none" }}>
{this.state.users.length
? this.state.users.map((user) => (
<li key={user.id}>
<Link
to={{
pathname: `/user/userdetail/${user.id}`,
state: user,
}}
style={{ display: "flex", justifyContent: "space-around" }}
>
<p>{user.username}</p>
</Link>
</li>
))
: "没有数据"}
</ul>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React, { Component } from "react";
import { getLoginMsg } from "../utils/local";
export default class Proflie extends Component {
render() {
let user = getLoginMsg();
return (
<div>
<h1 style={{ textAlign: "center" }}>用户信息</h1>
{user.username}
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from "react";
import { withRouter } from "../react-router-dom2.0";
class NavHead extends Component {
render() {
return (
<div
className="navbar-heading"
onClick={() => {
this.props.history.push("/");
}}
>
<div className="navbar-brand">Router 学习</div>
</div>
);
}
}
export default withRouter(NavHead);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from "react";
// import { Route, Link } from "react-router-dom";
import { Route, Link } from "../react-router-dom2.0";
// Route 里有三种方式 指定渲染内容 component; render; children
// component; render; 只有在路径匹配时才渲染内容
// children 不管路径匹不匹配都渲染
export default function ({ to, exact, children, activeClassName }) {
return (
<Route // Link 点击修改hash 触发重新渲染,使用 Route 包裹 获得 props 信息 以便比较操作
path={typeof to === "object" ? to.pathname : to}
exact={exact}
children={(props) => (
<Link to={to} className={props.match ? activeClassName : ""}>
{children}
</Link>
)}
/>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";
// import { Route, Redirect } from "react-router-dom";
import { Route, Redirect } from "../react-router-dom2.0";
import { getLoginMsg } from "../utils/local";
export default function ({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={(props) =>
getLoginMsg() ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location.pathname },
}}
/>
)
}
/>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
路由组建被传入的参数如下:

# 实现
react-router-dom 主要由 Router
, Route
这两个模块构成。Router
用来包裹根组件为子组件传递当前路由地址数据(用 context
实现),以供路由子组件对比是否匹配,如果匹配的话就显示,反之则隐藏;Router
中还绑定路由改变监听事件,每当路由改变时重新渲染组件。
import React, { Component } from "react";
import { RouteContext } from "./context";
export default class HashRouter extends Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.pathname,
state: null,
hash: null,
search: null,
},
};
}
componentDidMount() {
window.onpopstate = (e) => {
console.log(e);
if (this.blockMessage) {
let confirm = window.confirm(
this.blockMessage({ pathname: this.state.location.pathname })
);
if (!confirm) return;
}
this.setState({
location: {
...this.state.location,
pathname: window.location.pathname,
state: e.state,
},
});
};
window.onpushstate = (pathname, state) => {
this.setState({
location: {
...this.state.location,
pathname,
state,
},
});
};
}
render() {
let value = {
location: this.state.location,
history: {
push: (to) => {
// Prompt 阻止跳转实现
if (this.blockMessage) {
let confirm = window.confirm(
this.blockMessage({ pathname: this.state.location.pathname })
);
if (!confirm) return;
}
let pathname = typeof to === "object" ? to.pathname : to;
let state = typeof to === "object" ? to.state : null;
window.onpushstate(pathname, state);
window.history.pushState(state, null, pathname);
},
block: (message) => {
this.blockMessage = message; //暂存到 HashRouter类实例上
},
},
};
return (
<RouteContext.Provider value={value}>
{this.props.children}
</RouteContext.Provider>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import React, { Component } from "react";
import { RouteContext } from "./context";
export default class HashRouter extends Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.hash.slice(1),
state: null,
hash: null,
search: null,
},
};
this.routerState = undefined; // 暂存的 route 组件的 state
}
componentDidMount() {
// window.location.hash 不存在时 赋值为 “/”
window.location.hash = window.location.hash || "#/";
// 监听路由变化 window.onhashchange
window.onhashchange = (e) => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1),
state: this.routerState,
},
});
};
}
render() {
let value = {
location: this.state.location,
history: {
push: (to) => {
console.log(this.blockMessage);
// Prompt 阻止跳转实现
if (this.blockMessage) {
let confirm = window.confirm(
this.blockMessage(typeof to === "object" ? to : { pathname: to })
);
if (!confirm) return;
}
window.location.hash = typeof to === "object" ? to.pathname : to;
this.routerState = typeof to === "object" ? to.state : undefined;
},
block: (message) => {
this.blockMessage = message; //暂存到 HashRouter类实例上
},
},
};
return (
<RouteContext.Provider value={value}>
{this.props.children}
</RouteContext.Provider>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import React, { Component } from "react";
import { RouteContext } from "./context";
export default class Link extends Component {
static contextType = RouteContext;
render() {
return (
<a
onClick={() => this.context.history.push(this.props.to)}
className={this.props.className}
>
{this.props.children}
</a>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
const RouteContext = React.createContext();
export { RouteContext };
2
3
4
5
import React, { Component } from "react";
import { RouteContext } from "./context";
export default class Redirect extends Component {
static contextType = RouteContext;
componentDidMount() {
this.context.history.push(this.props.to);
}
render() {
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
import React, { Component } from "react";
import { RouteContext } from "./context";
import { pathToRegexp } from "path-to-regexp";
export default class Route extends Component {
static contextType = RouteContext;
render() {
let { pathname } = this.context.location;
let {
path = "/",
component: Component,
exact = false,
render,
children,
} = this.props;
let paramNames = [];
let regexp = pathToRegexp(path, paramNames, { end: exact });
let props = {
location: this.context.location,
history: this.context.history,
match: null,
};
let result = pathname.match(regexp);
if (result) {
// 处理占位符参数 存放到 match params里
paramNames = paramNames.map((param) => param.name); // 映射 paramNames
let [url, ...values] = result;
let params = {};
paramNames.forEach((name, i) => {
params[name] = values[i];
});
props.match = {
isExact: pathname === url, // 看匹配的字符串路径 是否与 pathname 相同
params, // 占位参数对象
path, // Route path 路由规则
url, // 匹配的字符串
};
if (Component) {
return <Component {...props} />;
} else if (render) {
return render(props);
} else if (children) {
return children(props); //match 为有值
} else {
return null;
}
}
if (children) {
return children(props); //match 为null
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React, { Component } from "react";
import { pathToRegexp } from "path-to-regexp";
import { RouteContext } from "./context";
// 循环 儿子 匹配则显示 只显示一个
export default class Switch extends Component {
static contextType = RouteContext;
render() {
let { pathname } = this.context.location;
let children = Array.isArray(this.props.children)
? this.props.children
: [this.props.children];
for (let i = 0; i < children.length; i++) {
const child = children[i];
let { path = "/", exact = false } = child.props;
let paramNames = [];
let regexp = pathToRegexp(path, paramNames, { end: exact });
if (pathname.match(regexp)) {
return child; // 组件的实例 外部定义时已经 经过了 createElment所以是实例
}
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react";
import { Route } from "../react-router-dom2.0";
// import { Route } from "react-router-dom";
export default function withRouter(Com) {
return class C extends Component {
render() {
return (
<Route
component={Com}
/>
);
}
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15