<a href="#/a">去a</a>
<a href="#/b">去b</a>
<script>
window.addEventListener('hashchange',()=>{
console.log(window.location.hash);
});
</script>
<script>
window.onpopstate = (event) => {
console.log({state:event.state,pathname:window.location.pathname,type:'popstate'});
};
window.onpushstate = (event) => {
console.log(event);
};
(function (history) {
var pushState = history.pushState;
history.pushState = function (state,title,pathname) {
if (typeof window.onpushstate == "function") {
window.onpushstate({state,pathname,type:'pushstate'});
}
return pushState.apply(history, arguments);
};
})(window.history);
//绑定事件处理函数.
setTimeout(()=>{
history.pushState({page: 1}, "title 1", "/page1");
},1000);
setTimeout(()=>{
history.pushState({page: 2}, "title 2", "/page2");
},2000);
setTimeout(()=>{
history.replaceState({page: 3}, "title 3", "/page3");
},3000);
setTimeout(()=>{
history.back();
},4000);
setTimeout(()=>{
history.go(1);
},5000);
</script>
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'));
src\components\Home.js
import React,{Component} from 'react';
export default class Home extends Component{
render() {
return (
<div>Home</div>
)
}
}
src\components\User.js
import React,{Component} from 'react';
export default class User extends Component{
render() {
console.log(JSON.stringify(this.props));
return (
<div>User</div>
)
}
}
/**
{
"history": {
"length": 4,
"action": "POP",
"location": {
"pathname": "/user",
"search": "?name=zfpx",
"hash": "#top"
}
},
"location": {
"pathname": "/user",
"search": "?name=zfpx",
"hash": "#top"
},
"match": {
"path": "/user",
"url": "/user",
"isExact": true,
"params": {}
}
}
*/
src\components\Profile.js
import React,{Component} from 'react';
export default class Profile extends Component{
render() {
return (
<div>Profile</div>
)
}
}
src\react-router-dom\index.js
import HashRouter from './HashRouter';
import Route from './Route';
export {
HashRouter,
Route
}
src\react-router-dom\HashRouter.js
import React, { Component } from 'react'
import Context from './context';
export default class HashRouter extends Component {
state = {
location:{pathname:window.location.hash.slice(1)}
}
componentWillMount(){
window.addEventListener('hashchange',()=>{
this.setState({
location:{
...this.state.location,
pathname:window.location.hash.slice(1) || '/'
}
});
});
window.location.hash = window.location.hash || '/';
}
render() {
let value = {
location:this.state.location
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
src\react-router-dom\Route.js
import React,{Component} from 'react';
import Context from './context';
export default class Route extends Component{
static contextType = Context;
render() {
let {path,component: Component}=this.props;
let pathname = this.context.location.pathname;
if (path == pathname) {
return <Component/>
} else {
return null;
}
}
}
src\react-router-dom\context.js
import {createContext} from 'react';
export default createContext();
let pathToRegExp = require('path-to-regexp');
let regx = pathToRegExp('/home',[],{end:true});
console.log(regx);// /^\/home\/?$/i
console.log(regx.test('/home'));
console.log(regx.test('/home/2'));
let pathToRegExp = require('path-to-regexp');
let regx2 = pathToRegExp('/home',[],{end:false});
console.log(regx2);// /^\/home\/?(?=\/|$)/i
console.log(regx2.test('/home'));
console.log(regx2.test('/home/'));
console.log(regx2.test('/home//'));
console.log(regx2.test('/home/2'));
let params = [];
let regx3 = pathToRegExp('/user/:id',params,{end:true});
console.log(regx3,params);
/**
/^\/user\/(?:([^\/]+?))\/?$/i
[ { name: 'id', optional: false, offset: 7 } ]
**/
表达式 | 含义 |
---|---|
() | 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容) |
(?:) | 表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来 |
表达式 | 含义 |
---|---|
(?=pattern) | 正向肯定查找(前瞻),后面必须跟着什么 |
(?!pattern) | 正向否定查找(前瞻),后面不能跟着什么 |
(?<=pattern) | 反向肯定条件查找(后顾),不捕获 |
(?<!pattern) | 反向否定条件查找(后顾) |
console.log('1a'.match(/\d(?=[a-z])/));
console.log('1@'.match(/\d(?![a-z])/));
console.log('a1'.match(/(?<=[a-z])\d/));
console.log('$1'.match(/(?<![a-z])\d/));
src\react-router-dom\Route.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
static contextType = Context;
render() {
let {path,component:Component,exact=false} = this.props;
let pathname = this.context.location.pathname;
let regxp = pathToRegexp(path,[],{end:exact});
let result = pathname.match(regxp);
console.log('regxp,pathname,result',regxp,pathname,result);
if(result){
return <Component/>;
}
return null;
}
}
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
<>
<Link to="/">Home</Link><Link to="/user">User</Link><Link to="/profile">Profile</Link>
<Route path="/" exact component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</>
</Router>
,document.getElementById('root'));
src/react-router-dom/Link.js
import React, { Component } from 'react'
import Context from './context';
export default class Link extends Component {
static contextType = Context;
render() {
return (
<a onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
src/react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
export {
HashRouter,
Route,
Link
}
src/react-router-dom/HashRouter.js
import React, { Component } from 'react'
import Context from './context';
export default class HashRouter extends Component {
state = {
location:{pathname:window.location.hash.slice(1) || '/'},
}
componentDidMount(){
window.location.hash = window.location.hash || '/';
window.addEventListener('hashchange',()=>{
this.setState({
location:{
...this.state.location,
pathname:window.location.hash.slice(1) || '/'
}
});
});
}
render() {
let value={
location: this.state.location,
history: {
push(to) {
window.location.hash=to;
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import 'bootstrap/dist/css/bootstrap.css';
ReactDOM.render(
<Router>
<>
<div className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-heading">
<div className="navbar-brand">珠峰架构</div>
</div>
<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>
<div className="container">
<div className="row">
<div className="col-md-12">
<Route path="/" exact component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</div>
</div>
</>
</Router>
,document.getElementById('root'));
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link,Redirect,Switch} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import 'bootstrap/dist/css/bootstrap.css';
ReactDOM.render(
<Router>
<>
<div className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-heading">
<div className="navbar-brand">珠峰架构</div>
</div>
<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>
<div className="container">
<div className="row">
<div className="col-md-12">
<Switch>
<Route path="/" exact component={Home} />
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
<Redirect to="/" />
</Switch>
</div>
</div>
</div>
</>
</Router>
,document.getElementById('root'));
src/react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Redirect from './Redirect';
import Switch from './Switch';
export {
HashRouter,
Route,
Link,
Redirect,
Switch
}
src/react-router-dom/Link.js
import React, { Component } from 'react'
import Context from './context';
export default class Link extends Component {
static contextType = Context;
render() {
return (
<a onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
src/react-router-dom/Route.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
static contextType = Context;
render() {
let {path,component:Component,exact=false} = this.props;
let pathname = this.context.location.pathname;
let regxp = pathToRegexp(path,[],{end:exact});
let result = pathname.match(regxp);
if(result){
return <Component {...this.context}/>;
}
return null;
}
}
src/react-router-dom/Redirect.js
import React, { Component } from 'react'
import Context from './context';
export default class Link extends Component {
static contextType = Context;
render() {
this.context.history.push(this.props.to);
return null;
}
}
src/react-router-dom/Switch.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component {
static contextType = Context;
render() {
let pathname = this.context.location.pathname;
for(let i=0;i<this.props.children.length;i++){
let child = this.props.children[i];
let {path='/',component:Component,exact=false} = child.props;
let regxp = pathToRegexp(path,[],{end:exact});
let result = pathname.match(regxp);
if(result){
return child;
}
}
return null;
}
}
src/components/User.js
import React,{Component} from 'react';
import {Link,Route} from '../react-router-dom';
import UserAdd from './UserAdd';
import UserDetail from './UserDetail';
import UserList from './UserList';
export default class User extends Component{
render() {
return (
<div className="row">
<div className="col-md-2">
<ul className="nav nav-stack">
<li><Link to="/user/list">用户列表</Link></li>
<li><Link to="/user/add">添加用户</Link></li>
</ul>
</div>
<div className="col-md-10">
<Route path="/user/add" component={UserAdd}/>
<Route path="/user/list" component={UserList}/>
<Route path="/user/detail/:id" component={UserDetail}/>
</div>
</div>
)
}
}
src/components/UserAdd.js
import React, { Component } from 'react'
export default class UserAdd extends Component {
constructor(){
super();
this.usernameRef = React.createRef();
}
handleSubmit = (event)=>{
event.preventDefault();
let username = this.usernameRef.current.value;
let usersStr = localStorage.getItem('users');
let users = usersStr?JSON.parse(usersStr):[];
users.push({id:Date.now()+'',username});
localStorage.setItem('users',JSON.stringify(users));
this.props.history.push('/user/list');
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input className="form-control" type="text" ref={this.usernameRef}/>
<button type="submit" className="btn btn-primary">提交</button>
</form>
)
}
}
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>
UserList
<ul className="list-group">
{
this.state.users.map((user,index)=>(
<li className="list-group-item" key={index}>
<Link to={{pathname:`/user/detail/${user.id}`,state :user}}>{user.username}</Link>
</li>
))
}
</ul>
</div>
)
}
}
src/components/UserDetail.js
import React, { Component } from 'react'
export default class UserDetail extends Component {
state = {
user:{}
}
componentDidMount(){
let user = this.props.location.state;
if(!user){
let usersStr = localStorage.getItem('users');
let users = usersStr?JSON.parse(usersStr):[];
let id = this.props.match.params.id;
user = users.find(user=>user.id === id);
}
if(user) this.setState({user});
}
render() {
let user = this.state.user;
return (
<div>
{user.id}:{user.username}
</div>
)
}
}
src/react-router-dom/HashRouter.js
import React, { Component } from 'react'
import Context from './context';
export default class HashRouter extends Component {
state = {
location:{pathname:window.location.hash.slice(1) || '/'}
}
locationState=undefined
componentDidMount(){
window.location.hash = window.location.hash || '/';
window.addEventListener('hashchange',()=>{
this.setState({
location:{
...this.state.location,
pathname:window.location.hash.slice(1) || '/',
state:this.locationState
}
});
});
}
render() {
let that = this;
let value = {
location:that.state.location,
history:{
push(to){
if(typeof to === 'object'){
let {pathname,state}= to;
that.locationState = state;
window.location.hash = pathname;
}else{
window.location.hash = to;
}
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
src/react-router-dom/Route.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
static contextType = Context;
render() {
let { path, component: Component, exact = false } = this.props;
let pathname = this.context.location.pathname;
let keys = [];
let regxp = pathToRegexp(path, keys, { end: exact });
let result = pathname.match(regxp);
if (result) {
let [url, ...values] = result;
keys = keys.map(item=>item.name);
let params = values.reduce((memo, val, index) => {
memo[keys[index]] = val;
return memo;
}, {});
let match = {
url:pathname,
isExact:pathname===url,
path,
params
}
let props = {
location: this.context.location,
history: this.context.history,
match
}
return <Component {...props} />;
}
return null;
}
}
src/react-router-dom/Switch.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component {
static contextType = Context;
render() {
let pathname = this.context.location.pathname;
let children = Array.isArray(this.props.children)?this.props.children:[this.props.children]
for(let i=0;i<children.length;i++){
let child = children[i];
let {path='/',exact=false} = child.props;
let regxp = pathToRegexp(path,[],{end:exact});
let result = pathname.match(regxp);
if(result){
return child;
}
}
return null;
}
}
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Link,Redirect,Switch} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Protected from './components/Protected';
import Login from './components/Login';
import 'bootstrap/dist/css/bootstrap.css';
ReactDOM.render(
<Router>
<>
<div className="navbar navbar-inverse">
<div className="container-fluid">
<div className="navbar-heading">
<div className="navbar-brand">珠峰架构</div>
</div>
<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>
<div className="container">
<div className="row">
<div className="col-md-12">
<Switch>
<Route path="/" exact component={Home} />
<Route path="/user" component={User} />
<Route path="/login" component={Login} />
<Protected path="/profile" component={Profile}/>
<Redirect to="/" />
</Switch>
</div>
</div>
</div>
</>
</Router>
,document.getElementById('root'));
src/react-router-dom/Route.js
let props = {
location: this.context.location,
history: this.context.history,
match
}
if(Component){
return <Component {...props} />;
}else if(render){
return render(props);
}else{
return null;
}
src/components/Protected.js
import React, { Component } 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}}}/>
)
}/>
)
src/components/Login.js
import React, { Component } from 'react'
export default class Login extends Component {
handleClick = ()=>{
localStorage.setItem('logined','true');
if(this.props.location.state)
this.props.history.push(this.props.location.state.from);
}
render() {
return (
<button className="btn btn-primary" onClick={this.handleClick}>登录</button>
)
}
}
src/index.js
<ul className="nav navbar-nav">
+ <li><MenuLink exact to="/">Home</MenuLink></li>
+ <li><MenuLink to="/user">User</MenuLink></li>
+ <li><MenuLink to="/profile">Profile</MenuLink></li>
</ul>
src/components/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 => (
<Link className={props.match?'active':''} to={to}>{children}</Link>
)
}
/>
)
src/components/MenuLink.css
.navbar-inverse .navbar-nav > li > .active{
background-color: green!important;
color:red!important;
}
src\react-router-dom\Link.js
import React, { Component } from 'react'
import Context from './context';
export default class Link extends Component {
static contextType = Context;
render() {
return (
+ <a {...this.props} onClick={()=>this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
src/react-router-dom/Route.js
import React, { Component } from 'react'
import Context from './context';
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
static contextType = Context;
render() {
let { path='/', component: Component, exact = false,render,children } = this.props;
let pathname = this.context.location.pathname;
let keys = [];
let regxp = pathToRegexp(path, keys, { end: exact });
let result = pathname.match(regxp);
+ let props = {
+ location: this.context.location,
+ history: this.context.history
+ }
if (result) {
let [url, ...values] = result;
keys = keys.map(item=>item.name);
let params = values.reduce((memo, val, index) => {
memo[keys[index]] = val;
return memo;
}, {});
let match = {
url:pathname,
isExact:pathname===url,
path,
params
}
+ 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;
+ }
}
}
+import NavHeader from './components/NavHeader';
<div className="navbar navbar-inverse">
<div className="container-fluid">
+ <NavHeader/>
<ul className="nav navbar-nav">
<MenuLink exact to="/">Home</MenuLink>
<MenuLink to="/user">User</MenuLink>
<MenuLink to="/profile">Profile</MenuLink>
</ul>
</div>
src/components/NavHeader.js
import React, { Component } from 'react'
import {withRouter} from '../react-router-dom';
class NavHeader extends Component {
render() {
return (
<div className="navbar-heading">
<div className="navbar-brand" onClick={()=>this.props.history.push('/')}>珠峰架构</div>
</div>
)
}
}
export default withRouter(NavHeader)
src/react-router-dom/withRouter.js
import React from 'react'
import {Route} from '../react-router-dom';
export default function (Component) {
return props=>(
<Route render={routeProps=><Component {...routeProps}>}/>
)
}
src/react-router-dom/index.js
import withRouter from './withRouter';
export {
HashRouter,
Route,
Link,
Redirect,
Switch,
withRouter
}
src/components/UserAdd.js
import React, { Component } from 'react'
import {Prompt} from '../react-router-dom';
export default class UserAdd extends Component {
constructor(){
super();
this.usernameRef = React.createRef();
}
state = {
isBlocking:false
}
handleSubmit = (event)=>{
event.preventDefault();
this.setState({
isBlocking:false
},()=>{
let username = this.usernameRef.current.value;
let usersStr = localStorage.getItem('users');
let users = usersStr?JSON.parse(usersStr):[];
users.push({id:Date.now()+'',username});
localStorage.setItem('users',JSON.stringify(users));
this.props.history.push('/user/list');
})
}
render() {
let {isBlocking}=this.state;
return (
<form onSubmit={this.handleSubmit}>
<Prompt
when={isBlocking}
message={location=>`你确定要跳转到${location.pathname}吗?`}
/>
<input className="form-control" type="text"
onChange={event => {
this.setState({isBlocking:event.target.value.length>0});
}}
ref={this.usernameRef}/>
<button type="submit" className="btn btn-primary">提交</button>
</form>
)
}
}
src/react-router-dom/HashRouter.js
import React, { Component } from 'react'
import Context from './context';
export default class HashRouter extends Component {
state = {
location: { pathname: window.location.hash.slice(1) || '/' }
}
locationState = undefined
getMessage=null
componentDidMount() {
window.location.hash = window.location.hash || '/';
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/',
state: this.locationState
}
});
});
}
render() {
let that = this;
let value = {
location: that.state.location,
history: {
push(to) {
if (that.getMessage) {
let allow=window.confirm(that.getMessage(that.state.location)+`,并且跳转到${typeof to === 'object'?to.pathname:to}吗?`);
if (!allow) return;
}
if (typeof to === 'object') {
let { pathname, state } = to;
that.locationState = state;
window.location.hash = pathname;
} else {
window.location.hash = to;
}
},
block(message) {
that.getMessage=message;
},
unblock() {
that.getMessage=null;
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
src/react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Redirect from './Redirect';
import Switch from './Switch';
import withRouter from './withRouter';
import Prompt from './Prompt';
export {
HashRouter,
Route,
Link,
Redirect,
Switch,
withRouter,
Prompt
}
src/react-router-dom/Prompt.js
import React from 'react'
import Context from './context';
export default class Prompt extends React.Component{
static contextType = Context;
componentWillUnmount() {
this.history.unblock();
}
render() {
this.history=this.context.history;
const {when,message}=this.props;
if (when) {
this.history.block(message);
} else {
this.history.block(null);
}
return null;
}
}
src/index.js
import {BrowserRouter as Router,Route,Redirect,Switch} from './react-router-dom';
src/react-router-dom/BrowserRouter.js
import React, { Component } from 'react'
import Context from './context';
(function (history) {
var pushState = history.pushState;
history.pushState = function (state,title,pathname) {
if (typeof window.onpushstate == "function") {
window.onpushstate(state,pathname);
}
return pushState.apply(history, arguments);
};
})(window.history);
export default class BrowserRouter extends Component {
state = {
location: { pathname: '/' }
}
getMessage = null
componentDidMount() {
window.onpopstate = (event) => {
this.setState({
location: {
...this.state.location,
pathname:document.location.pathname,
state:event.state
}
});
};
window.onpushstate = (state,pathname) => {
this.setState({
location: {
...this.state.location,
pathname,
state
}
});
};
}
render() {
let that = this;
let value = {
location: that.state.location,
history: {
push(to) {
if (that.block) {
let allow = window.confirm(that.getMessage(that.state.location));
if (!allow) return;
}
if (typeof to === 'object') {
let { pathname, state } = to;
window.history.pushState(state, '', pathname);
} else {
window.history.pushState('', '', to);
}
},
block(getMessage) {
that.block = getMessage;
},
unblock() {
that.block = null;
}
}
}
return (
<Context.Provider value={value}>
{this.props.children}
</Context.Provider>
)
}
}
src/react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
import Redirect from './Redirect';
import Switch from './Switch';
import withRouter from './withRouter';
import Prompt from './Prompt';
import BrowserRouter from './BrowserRouter';
export {
HashRouter,
Route,
Link,
Redirect,
Switch,
withRouter,
Prompt,
BrowserRouter
}