React路由
- 不同的路徑渲染不同的元件
- 有兩種實現方式
- HashRouter:利用hash實現路由切換
- BrowserRouter:實現h5 Api實現路由的切換
1.1 hash
<a href="#/a">去a</a>
<a href="#/b">去b</a>
<script>
window.addEventListener('hashchange',()=>{
console.log(window.location.hash);
});
</script>
複製程式碼
1.2 history
- history物件提供了操作瀏覽器會話歷史的介面。
- history物件持續追蹤著一組location
- 除了一組location外,history也儲存一個索引值,用來指向當前所對應的location
- history
- History
- onpopstate
2.跑通路由
2.1 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route} from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
<div>
<Route path="/" component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</Router>
,document.getElementById('root'));
複製程式碼
2.2 Home.js
components/Home.js
import React,{Component} from 'react';
export default class Home extends Component{
render() {
return (
<div>Home</div>
)
}
}
複製程式碼
2.3 Profile.js
components/Profile.js
import React,{Component} from 'react';
export default class Profile extends Component{
render() {
return (
<div>Profile</div>
)
}
}
複製程式碼
2.4 User.js components/User.js
import React,{Component} from 'react';
export default class User extends Component{
render() {
return (
<div>User</div>
)
}
}
複製程式碼
2.5 this.props http://localhost:3000/#/user?name=zyfx#top
{
"match": {
"path": "/user/:id", //匹配路徑
"url": "/user/1", //位址列中的url
"isExact": true, //是否精確匹配
"params": {"id": "1"} // 路徑引數物件
},
"location": {
"pathname": "/user/1", //路徑名
"search": "?name=zyfx", //查詢字串
"hash": "#top" //hash值
"state":undefined
},
"history": {
"length": 6, //歷史長度
"action": "POP", //動作
"location": { //當前應用所處的位置
"pathname": "/user/1",
"search": "?name=zyfx",
"hash": "#top",
"state":undefined //location可以擁有與之相關的狀態。這是一些固定的資料,並且不存在於URL之中
},
"go":f go(n),//是一個強大的方法,幷包含了goForward與goBack的功能。傳入負數則退後,傳入正數則向前
"goBack":f goBack(),//返回一層頁面。實際上是將history的索引值減1
"goForward":f goForward(),//與goBack相對。向前一層頁面
"listen":f listen(listener),//採用觀察者模式,在location改變時,history會發出通知
"push":f push(path,state),//方法使能你跳轉到新的location
"replace":f replace(path,state)//replace方法與push相似,但它並非新增location,而是替換當前索引上的位置,重定向時要使用replace,
"createHref":f createHref(location)
}
}
複製程式碼
2.6 createHref
const location = {
pathname: '/user',
search: '?id=1',
hash: '#bottom'
}
const url = history.createHref(location)
const link = document.createElement('a')
a.href = url
// <a href='/user?id=1#bottom'></a>
複製程式碼
3.1 index.js
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
<div>
<Route path="/" component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</Router>
,document.getElementById('root'));
複製程式碼
3.2 index.js
src/react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
export {
HashRouter,
Route
}
複製程式碼
3.3 context.js
src/react-router-dom/context.js
import React from 'react';
// React16.3
let {Provider,Consumer}=React.createContext();
export {Provider,Consumer};
複製程式碼
3.4 HashRouter.js
src/react-router-dom/HashRouter.js
import React,{Component} from 'react';
import {Provider} from './context';
export default class HashRouter extends Component{
state={
location: {
pathname:window.location.hash?window.location.hash.slice(1):'/'
}
}
componentDidMount() {
//預設hash沒有的時候跳轉到/
window.location.hash=window.location.hash||'/';
//監聽hash值變化
window.addEventListener('hashchange',() => {
this.setState({
location: {
...this.state.location,
pathname:window.location.hash?window.location.hash.slice(1):'/'
}});
});
}
render() {
let value={
location:this.state.location
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
}
複製程式碼
3.5 Route.js
src/react-router-dom/Route.js
import React,{Component} from 'react';
import {Consumer} from './context';
export default class Route extends Component{
render() {
let {path,component: Component}=this.props;
return (
<Consumer>
{
value => {
let {pathname}=value.location;
if (path == pathname) {
return <Component/>
} else {
return null;
}
}
}
</Consumer>
)
}
}
複製程式碼
4. path-to-regexp
把一個路徑轉換成正規表示式
let pathToRegexp=require('path-to-regexp');
//let regex=pathToRegexp('/user',[],{end: true});// /^\/user(?:\/)?$/i
//let regex=pathToRegexp('/user',[],{end: false});// //^\/user(?:\/(?=$))?(?=\/|$)/i
let keys=[];
let regex=pathToRegexp('/user/:id',keys,{end: false});///^\/user\/([^\/]+?)(?:\/(?=$))?(?=\/|$)/i
// (?:) 是 不想被捕獲的時候使用
// (?=pattern) 零寬正向先行斷言(zero-width positive lookahead assertion)
console.log(regex);
console.log(keys);
//console.log(regex.test('/user')); //true
//console.log(regex.test('/user/1')); //true
console.log(regex.test('/user/1')); //true
複製程式碼
5. 正則匹配路徑
5.1 Route.js
src/react-router-dom/Route.js
import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component{
render() {
let {path,component: Component}=this.props;
let regexp=pathToRegexp(path,[],{end:false});
return (
<Consumer>
{
value => {
let {pathname}=value.location;
if (regexp.test(pathname)) {
return <Component/>
} else {
return null;
}
}
}
</Consumer>
)
}
}
複製程式碼
6.exact 精確匹配
6.1 index.js
ReactDOM.render(
<Router>
<div>
+ `<Route exact={true} path="/" component={Home} />`
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</Router>
,document.getElementById('root'));
複製程式碼
6.2 Route.js
src/react-router-dom/Route.js
export default class Route extends Component{
render() {
+ `let {path,component: Component,exact=false}=this.props`;
+ `let regexp=pathToRegexp(path,[],{end:exact});`
return (
<Consumer>
{
value => {
let {pathname}=value.location;
if (regexp.test(pathname)) {
return <Component/>
} else {
return null;
}
}
}
</Consumer>
)
}
}
複製程式碼
7. Link
7.1 Link.js
src/react-router-dom/Link.js
import React,{Component} from 'react';
import {Consumer} from './context';
export default class Link extends Component{
render() {
return (
<Consumer>
{
value => {
let {history: {push}}=value;
return (
<a onClick={()=>push(this.props.to)}>{this.props.children}</a>
)
}
}
</Consumer>
)
}
}
複製程式碼
7.2 index.js #
src/index.js
import 'bootstrap/dist/css/bootstrap.css'
ReactDOM.render(
<Router>
<div>
<nav className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-header">
<a className="navbar-brand" >學生管理系統</a>
</div>
<div id="navbar" className="collapse navbar-collapse">
<ul className="nav navbar-nav">
<li><Link to="/">Home</Link></li>
<li><Link to="/user">User</Link></li>
<li><Link to="/profile">Profile</Link></li>
</ul>
</div>
</div>
</nav>
<div className="container">
<Route exact={true} path="/" component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</div>
</Router>
,document.getElementById('root'));
複製程式碼
7.3 HashRouter.js
src/react-router-dom/HashRouter.js
render() {
let value={
location: this.state.location,
history: {
push(to) {
window.location.hash=to;
}
}
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
複製程式碼
8.Redirect&Switch
8.1 index.js
src/index.js
<div className="container">
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile} />
<Redirect to="/"/>
</Switch>
</div>
複製程式碼
8.2 Redirect.js
src/react-router-dom/Redirect.js
import React,{Component} from 'react';
import {Consumer} from './context';
export default class Redirect extends Component{
render() {
return (
<Consumer>
{
value => {
value.history.push(this.props.to);
return null;
}
}
</Consumer>
)
}
}
複製程式碼
8.3 Switch.js
src/react-router-dom/Switch.js
import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
render() {
return (
<Consumer>
{
value => {
let {location: {pathname}}=value;
let children=this.props.children;
for (let i=0;i<children.length;i++){
let child=children[i];
let {path="/",exact=false}=child.props;
let regexp=pathToRegexp(path,[],{end: exact});
if (regexp.test(pathname)) {
return child;
}
}
return null;
}
}
</Consumer>
)
}
}
複製程式碼
9.頁面跳轉
9.1 User.js
src/components/User.js
<Switch>
<Route path="/user/add" component={UserAdd} />
<Route path="/user/list" component={UserList} />
<Route path="/user/detail/:id" component={UserDetail}/>
</Switch>
複製程式碼
9.2 HashRouter.js
src/react-router-dom/HashRouter.js
render() {
let value={
location: this.state.location,
history: {
push(to) {
window.location.hash=to;
}
}
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
複製程式碼
9.3 Route.js
src/react-router-dom/Route.js
import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component{
constructor(props) {
super(props);
}
render() {
let {path,component: Component,exact=false}=this.props;
return (
<Consumer>
{
value => {
let {pathname}=value.location;
let keys=[];
let regexp=pathToRegexp(path,keys,{end: exact});
keys = keys.map(item=>item.name)
let result=pathname.match(regexp);
if (result) {
let [,...values]=result;
let match={
params: keys.reduce((params,key,idx) => {
params[key]=values[idx];
return params;
},{}),
path,
url: pathname
};
let props={
location: value.location,
history: value.history,
match
}
return <Component {...props}/>
} else {
return null;
}
}
}
</Consumer>
)
}
}
複製程式碼
9.4 Switch.js
src/react-router-dom/Switch.js
import React,{Component} from 'react';
import {Consumer} from './context';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
render() {
return (
<Consumer>
{
value => {
let {location: {pathname}}=value;
let children=this.props.children;
for (let i=0;i<children.length;i++){
let child=children[i];
let {path="/",exact=false}=child.props;
let regexp=pathToRegexp(path,[],{end: exact});
if (regexp.test(pathname)) {
return child;
}
}
return null;
}
}
</Consumer>
)
}
}
複製程式碼
9.5 UserAdd.js
src/components/UserAdd.js
import React,{Component} from 'react';
export default class UserAdd extends Component{
handleSubmit=(event) => {
event.preventDefault();
let username=this.username.value;
let email=this.email.value;
let user={username,email};
let usersStr=localStorage.getItem('users');
let users=usersStr? JSON.parse(usersStr):[];
user.id = users.length>0? users[users.length-1].id+1:1;
users.push(user);
localStorage.setItem('users',JSON.stringify(users));
this.props.history.push('/user/list');
}
render() {
return (
<div className="row">
<div className="col-md-12">
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="username">使用者名稱</label>
<input type="text" className="form-control" ref={input=>this.username = input}/>
</div>
<div className="form-group">
<label htmlFor="email">郵箱 </label>
<input type="email" className="form-control" ref={input=>this.email = input}/>
</div>
<div className="form-group">
<input type="submit" className="btn btn-primary"/>
</div>
</form>
</div>
</div>
)
}
}
複製程式碼
9.6 UserDetail.js
src/components/UserDetail.js
import React,{Component} from 'react';
export default class UserList extends Component{
state={
user: {}
}
componentDidMount() {
let usersStr=localStorage.getItem('users');
let users=usersStr? JSON.parse(usersStr):[];
let user = users.find(user => user.id==this.props.match.params.id);
this.setState({user});
}
render() {
let {user}=this.state;
return (
<div className="row">
<div className="col-md-12">
<div>ID:{user.id}</div>
<div>使用者名稱:{user.username}</div>
<div>郵箱:{user.email}</div>
</div>
</div>
)
}
}
複製程式碼
9.7 UserList.js
src/components/UserList.js
import React,{Component} from 'react';
import {Link} from '../react-router-dom';
export default class UserList extends Component{
state={
users:[]
}
componentDidMount() {
let usersStr=localStorage.getItem('users');
let users=usersStr? JSON.parse(usersStr):[];
this.setState({users});
}
render() {
return (
<div className="row">
<div className="col-md-12">
<ul className="list-group">
{
this.state.users.map(user => (
<li className="list-group-item" key={user.id}>
<Link to={`/user/detail/${user.id}`}>{user.username}</Link>
</li>
))
}
</ul>
</div>
</div>
)
}
}
複製程式碼
###10. 受保護的路由
10.1 src/index.js
<Switch>
<Route exact={true} path="/" component={Home} />
<Route path="/user" component={User} />
+ <Route path="/login" component={Login} />
+ <Protected path="/profile" component={Profile} />`
<Redirect to="/"/>
</Switch>
複製程式碼
10.2 HashRouter.js src/react-router-dom/HashRouter.js
render() {
let self=this;
let value={
location: self.state.location,
history: {
+ push(to) {
+ if (typeof to == 'object') {
+ let {pathname,state}=to;
+ self.setState({
+ location: {
+ ...self.state.location,state,pathname
+ }
+ },() => {
+ window.location.hash=pathname;
+ });
+ } else {
+ window.location.hash=to;
+ }
+ }
+ }
}
return (
<Provider value={value}>
{this.props.children}
</Provider>
)
}
複製程式碼
10.3 Route.js
src/react-router-dom/Route.js
+ if (Component) {
+ return <Component {...props}/>
+ } else if (render) {
+ return render(props);
+ } else {
+ return null;
+ }
複製程式碼
10.4 Login.js
src/components/Login.js
+ import React from 'react'
+ export default class Login extends React.Component{
+ handleClick=() => {
+ localStorage.setItem('logined',true);
+ this.props.history.push(this.props.location.state.from);
+ }
+ render() {
+ return (
+ <div>
+ <button
+ className="btn btn-primary"
+ onClick={this.handleClick}
+ >登入</button>
+ </div>
+ )
+ }
+}
複製程式碼
10.5 Protected.js
src/components/Protected.js
+ import React from 'react'
+ import {Route,Redirect} from '../react-router-dom';
+ export default ({component:Component,...rest}) => (
+ <Route
+ {...rest}
+ render={
+ props => (
+ localStorage.getItem('logined')?
+ <Component {...props} />:
+ <Redirect to={{pathname: '/login',state: {from: props.location.pathname}}} />
+ )
+ }
+ />
+ )
複製程式碼
11. 自定義導航
11.1 index.js
src/index.js
<ul className="nav navbar-nav">
+ <MenuLink exact to="/">Home</MenuLink>
+ <MenuLink to="/user">User</MenuLink>
+ <MenuLink to="/profile">Profile</MenuLink>
</ul>
複製程式碼
11.2 Route.js
src/react-router-dom/Route.js
let props={
location: value.location,
history: value.history
}
if (result) {
let [,...values]=result;
let match={
params: keys.reduce((params,key,idx) => {
params[key]=values[idx];
return params;
},{}),
path,
url: pathname
};
props.match = match;
if (Component) {
return <Component {...props}/>
} else if (render) {
return render(props);
} else if(children){
return children(props);
}else {
return null;
}
} else if(children){
return children(props);
} else {
return null;
}
複製程式碼
11.3 MenuLink.js
import React from 'react'
import {Route,Link} from '../react-router-dom';
import './MenuLink.css'
export default ({to,exact,children}) => (
<Route
path={to}
exact={exact}
children={
props => (
<li className={props.match?'active':''}><Link to={to}>{children}</Link></li>
)
}
/>
)
複製程式碼
11.4 MenuLink.css
.navbar-inverse .navbar-nav > .active > a{
background-color: orange;
}
複製程式碼
12. 防止跳轉
12.1 UserAdd.js
src/components/UserAdd.js
import React,{Component} from 'react';
import {Prompt} from '../react-router-dom';
export default class UserAdd extends Component{
+ state={
+ isBlocking:false
+ }
handleSubmit=(event) => {
+ event.preventDefault();
+ this.setState({
+ isBlocking:false
+ },() => {
let username=this.username.value;
let email=this.email.value;
let user={username,email};
let usersStr=localStorage.getItem('users');
let users=usersStr? JSON.parse(usersStr):[];
user.id = users.length>0? users[users.length-1].id+1:1;
users.push(user);
localStorage.setItem('users',JSON.stringify(users));
this.props.history.push('/user/list');
});
}
render() {
+ let {isBlocking}=this.state;
return (
<div className="row">
<div className="col-md-12">
<form onSubmit={this.handleSubmit}>
+ <Prompt
+ when={isBlocking}
+ message={
+ location=>`你確定要跳轉到${location.pathname}嗎?`
+ }
+ />
<div className="form-group">
<label htmlFor="username">使用者名稱</label>
<input type="text"
onChange={
event => this.setState({isBlocking:event.target.value.length>0})
}
className="form-control" ref={input => this.username=input} />
</div>
<div className="form-group">
<label htmlFor="email">郵箱 </label>
<input
onChange={
event => this.setState({isBlocking:event.target.value.length>0})
}
type="email" className="form-control" ref={input => this.email=input} />
</div>
<div className="form-group">
<input type="submit" className="btn btn-primary"/>
</div>
</form>
</div>
</div>
)
}
}
複製程式碼
12.2 HashRouter.js
src/react-router-dom/HashRouter.js
let value={
location: self.state.location,
history: {
push(to) {
+ if (self.block) {
+ let allow=window.confirm(self.block(typeof to=='object'? to:{pathname:to}));
+ if (!allow) return;
+ }
if (typeof to == 'object') {
let {pathname,state}=to;
self.setState({
location: {
...self.state.location,state,pathname
}
},() => {
window.location.hash=pathname;
});
} else {
window.location.hash=to;
}
},
+ block(message) {
+ self.block=message;
+ },
+ unblock() {
+ self.block=null;
+ }
}
}
複製程式碼
12.3 Prompt.js
src/react-router-dom/Prompt.js
import React from 'react'
import {Consumer} from './context'
export default class Prompt extends React.Component{
componentWillUnmount() {
this.history.unblock();
}
render() {
return (
<Consumer>
{
value => {
this.history=value.history;
const {when,message}=this.props;
if (when) {
this.history.block(message);
} else {
this.history.block(null);
}
}
}
</Consumer>
);
}
}
複製程式碼
13. withRouter
13.1 index.js
rc/index.js
<div className="container-fluid">
+ <Header/>
<div id="navbar" className="collapse navbar-collapse">
複製程式碼
13.2 Route.js
src/react-router-dom/Route.js
+ let {path='/',component: Component,exact=false,render,children}=this.props;
return (
<Consumer>
複製程式碼
13.3 index.js
src/react-router-dom/index.js
+import withRouter from './withRouter';
export {
HashRouter,
Route,
Link,
Redirect,
Switch,
Prompt,
+ withRouter
}
複製程式碼
13.4 Header.js
src/components/Header.js
import React from 'react'
import {withRouter} from '../react-router-dom';
class Header extends React.Component{
render() {
return (
<div className="navbar-header">
<a
onClick={()=>this.props.history.push('/')}
className="navbar-brand" >
學生管理系統
</a>
</div>
)
}
}
export default withRouter(Header);
複製程式碼
13.5 withRouter.js
src/react-router-dom/withRouter.js
import React from 'react'
import {Consumer} from './context';
import Route from './Route';
export default function (Component) {
class Proxy extends React.Component{
render() {
return (
<Consumer>
{
value => {
return <Route component={Component}/>
}
}
</Consumer>
)
}
}
return Proxy;
}
複製程式碼