public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#root {
border: 1px solid red;
padding: 10px;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="root"></div>
<ul>
<li><a href="#/a">/a</a></li>
<li><a href="#/b">/b</a></li>
</ul>
<script>
function updateContent() {
var root = document.getElementById('root');
var pathname = window.location.hash.slice(1); // 删除最前面的符号
root.innerHTML = pathname || 'Home'; // 如果没有hash,则显示'Home'
}
window.addEventListener('hashchange', updateContent);
// 页面加载时更新内容
updateContent();
</script>
</body>
</html>
History
栈,执行pushState
函数可压入设定的url
至栈顶,同时修改当前指针back
和forward
操作时,history栈大小并不会改变(history.length不变),仅仅移动当前指针的位置<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#root {
border: 1px solid red;
height: 20px;
padding: 10px;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
var historyObj = window.history;
var root = document.getElementById('root');
function updateContent(pathname) {
root.innerHTML = pathname;
}
window.addEventListener('popstate', (event) => {
updateContent(window.location.pathname);
});
(function (historyObj) {
let oldPushState = history.pushState;
historyObj.pushState = function (state, title, pathname) {
let result = oldPushState.apply(history, arguments);
updateContent(pathname);
return result;
}
})(historyObj);
setTimeout(() => historyObj.pushState({ page: 1 }, 'page1', '/page1'), 1000);
setTimeout(() => historyObj.pushState({ page: 2 }, 'page2', '/page2'), 2000);
setTimeout(() => historyObj.pushState({ page: 3 }, 'page3', '/page3'), 3000);
setTimeout(() => historyObj.back(), 4000);
setTimeout(() => historyObj.pushState({ page: 4 }, 'page4', '/page4'), 5000);
setTimeout(() => historyObj.go(1), 6000);
</script>
</body>
</html>
npm i react-router-dom --save
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} exact/>
<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 React.Component{
render(){
console.log(this.props);
return (
<div>
<p>Home</p>
<button onClick={()=>this.props.history.push({pathname:'/user',state:{id:1}})}>跳转到/user</button>
</div>
)
}
}
src\components\User.js
import React,{Component} from 'react';
export default class User extends React.Component{
render(){
console.log(this.props.location.state);
return (
<div>
<p>User</p>
<button onClick={()=>this.props.history.goBack()}>返回</button>
</div>
)
}
}
/**
{
"history": {
"length": 3,
"action": "POP",
"location": {
"pathname": "/user",
"search": "",
"hash": ""
}
},
"location": {
"pathname": "/user",
"search": "",
"hash": ""
},
"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>
)
}
}
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} exact/>
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
</div>
</Router>
,document.getElementById('root'));
src\react-router\RouterContext.js
import React from 'react'
export default React.createContext({});
src\react-router\Router.js
import React from 'react'
import RouterContext from './RouterContext';
class Router extends React.Component{
constructor(props){
super(props);
this.state = {
location:props.history.location
}
//当路径发生的变化的时候执行回调
this.unlisten = props.history.listen((location)=>{
this.setState({location});
});
}
componentWillUnmount(){
this.unlisten&&this.unlisten();
}
render(){
let value = {//通过value向下层传递数据
location:this.state.location,
history:this.props.history
}
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
)
}
}
export default Router;
src\react-router\Route.js
import React from 'react'
import RouterContext from './RouterContext';
class Route extends React.Component{
static contextType = RouterContext
render(){
const {history,location} = this.context;
const {path,component:RouteComponent} = this.props;
const match = location.pathname === path;
let routeProps = {history,location};
let element=null;
if(match){
element= <RouteComponent {...routeProps}/>
}
return element;
}
}
export default Route;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
src\react-router-dom\BrowserRouter.js
import React from 'react'
import {Router} from '../react-router';
import {createBrowserHistory} from 'history';
//创建相应的历史对象,并且传入Router组件,原样渲染子组件
class BrowserRouter extends React.Component{
//不管是哪种创建历史对象的方法,得到的history 长的都一样,都像window.history
history = createBrowserHistory(this.props)//window.history
render(){
return (
<Router history={this.history}>
{this.props.children}
</Router>
)
}
}
export default BrowserRouter;
src\react-router-dom\HashRouter.js
import React from 'react'
import {Router} from '../react-router';
import {createHashHistory} from 'history';
class HashRouter extends React.Component{
history = createHashHistory()//hash实现
render(){
return (
<Router history={this.history}>
{this.props.children}
</Router>
)
}
}
export default HashRouter;
src\react-router-dom\index.js
export * from '../react-router';
export {default as HashRouter} from './HashRouter';
export {default as BrowserRouter} from './BrowserRouter';
src\history\createBrowserHistory.js
function createBrowserHistory(){
const globalHistory = window.history;
let listeners = [];//存放所有的监听函数
let state;
function listen(listener){
listeners.push(listener);
return ()=>{
listeners = listeners.filter(item=>item!=listener);
}
}
function go(n){
globalHistory.go(n);
}
window.addEventListener('popstate',()=>{//TODO
let location = {state:globalHistory.state,pathname:window.location.pathname};
//当路径改变之后应该让history的监听函数执行,重新刷新组件
notify({action:"POP",location});
});
function goBack(){
go(-1);
}
function goForward(){
go(1);
}
function notify(newState){
//把newState上的属性赋值到history对象上
Object.assign(history,newState);
history.length = globalHistory.length;//路由历史栈中历史条目的长度
listeners.forEach(listener=>listener(history.location));//通知监听函数执行,参数是新的location
}
function push(pathname,nextState){//TODO
const action = 'PUSH';//action表示是由于什么样的动作引起了路径的变更
if(typeof pathname === 'object'){
state = pathname.state;
pathname = pathname.pathname;
}else{
state=nextState;//TODO
}
globalHistory.pushState(state,null,pathname);//我们已经 跳转路径
let location = {state,pathname};
notify({action,location});
}
const history = {
action:'POP',
go,
goBack,
goForward,
push,
listen,
location:{pathname:window.location.pathname,state:window.location.state}
}
return history;
}
export default createBrowserHistory;
src\history\createHashHistory.js
/**
* hash不能使用 浏览器的history对象了
* @returns
*/
function createHashHistory(){
let stack = [];//类似于历史栈 里面存放都是路径
let index = -1;//栈的指针,默认是-1
let action = 'POP';//动作
let state ;//最新的状态
let listeners = [];//监听函数的数组
function listen(listener){
listeners.push(listener);
return ()=>{
listeners = listeners.filter(item=>item!=listener);
}
}
function go(n){
action = 'POP';
index+=n;//更改栈顶的指针
let nextLocation = stack[index];//取出指定索引对应的路径对象
state= nextLocation.state;//取出此location对应的状态
window.location.hash = nextLocation.pathname;//修改hash值 ,从而修改当前的路径
}
let hashChangeHandler = ()=>{
let pathname = window.location.hash.slice(1);//取出最新的hash值对应的路径 #/user
Object.assign(history,{action,location:{pathname,state}});
if(action === 'PUSH'){//说明是调用push方法,需要往历史栈中添加新的条目
stack[++index]=history.location;
}
listeners.forEach(listener=>listener(history.location));
}
function push(pathname,nextState){
action = 'PUSH';
if(typeof pathname ==='object'){
state = pathname.state;
pathname = pathname.pathname
}else{
state = nextState;
}
window.location.hash = pathname;
}
//当hash发生变化的话,会执行回调
window.addEventListener('hashchange',hashChangeHandler);
function goBack(){
go(-1);
}
function goForward(){
go(1);
}
const history = {
action:'POP',
go,
goBack,
goForward,
push,
listen,
location:{},
location:{pathname:'/',state:undefined}
}
if(window.location.hash){//如果初始的情况下,如果hash是有值的
action = 'PUSH';
hashChangeHandler();
}else{
window.location.hash = '/';
}
return history;
}
export default createHashHistory;
src\history\index.js
export {default as createBrowserHistory} from './createBrowserHistory';
export {default as createHashHistory} from './createHashHistory';
src\react-router-dom\BrowserRouter.js
import React from 'react'
import {Router} from '../react-router';
+import {createBrowserHistory} from '../history';
//创建相应的历史对象,并且传入Router组件,原样渲染子组件
class BrowserRouter extends React.Component{
//不管是哪种创建历史对象的方法,得到的history 长的都一样,都像window.history
history = createBrowserHistory(this.props)//window.history
render(){
return (
<Router history={this.history}>
{this.props.children}
</Router>
)
}
}
export default BrowserRouter;
src\react-router-dom\HashRouter.js
import React from 'react'
import {Router} from '../react-router';
import {createHashHistory} from '../history';
class HashRouter extends React.Component{
history = createHashHistory()//hash实现
render(){
return (
<Router history={this.history}>
{this.props.children}
</Router>
)
}
}
export default HashRouter;
let pathToRegExp = require('path-to-regexp');
let regxp = pathToRegExp('/home',[],{end:true});
console.log(regxp);// /^\/home\/?$/i
console.log(regxp.test('/home'));
console.log(regxp.test('/home/2'));
let pathToRegExp = require('path-to-regexp');
let regExp = pathToRegExp('/home',[],{end:false});
console.log(regExp);// /^\/home\/?(?=\/|$)/i
console.log(regExp.test('/home'));
console.log(regExp.test('/home/'));
console.log(regExp.test('/home//'));
console.log(regExp.test('/home/2'));
let params = [];
let regExp = pathToRegExp('/user/:id',params,{end:true});
console.log(regExp,params);
/**
/^\/user\/(?:([^\/]+?))\/?$/i
[ { name: 'id', optional: false, offset: 7 } ]
**/
useLocation
表达式 | 含义 |
---|---|
() | 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容) |
(?:) | 表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来 |
(?<name>...) |
表示命名捕获分组,反向引用一个命名分组的语法是 \k<name>` ,在 replace() 方法的替换字符串中反向引用是用 $ |
//分组获取
console.log('1ab'.match(/1[a-z]([b-c])/));
//分组不捕获
console.log('1ab'.match(/1[a-z](?:[a-z])/));
//?<x> 表示命名捕获分组
console.log(/(?<x>\d{2})-(?<y>\d{2})/.exec('11-22'));
console.log('11-22'.replace(/(?<x>\d{2})-(?<y>\d{2})/,"$<y>-$<x>"));
表达式 | 含义 |
---|---|
(?=pattern) | 正向肯定查找(前瞻),后面必须跟着什么 |
(?!pattern) | 正向否定查找(前瞻),后面不能跟着什么 |
(?<=pattern) |
反向肯定条件查找(后顾),不捕获 |
(?<!pattern) |
反向否定条件查找(后顾) |
//会消耗掉字符的
//console.log('1a'.match(/\d[a-z][a-z]/));
//?= 正向肯定查找 不消费字符 正向前瞻
//console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向肯定前瞻
console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向否定前瞻
console.log('1a'.match(/\d(?![A-Z])[a-z]/));
//反向肯定前瞻
console.log('1a'.match(/(?<=[a-z])\d[a-z]/));
//反向否定前瞻
console.log('1a'.match(/(?<![A-Z])\d[a-z]/));
let array = ['1ab'];
array.index = 0;
array.input = '1ab';
array.groups = undefined;
console.log(array);
src\react-router\matchPath.js
import pathToRegexp from 'path-to-regexp';
function compilePath(path,options){
const keys = [];
const regexp = pathToRegexp(path,keys,options);
return {keys,regexp};
}
/**
* @param {*} pathname 浏览器栏中的真实路径
* @param {*} options 匹配的参数 path exact strict sensitive
*/
function matchPath(pathname,options = {}){
let {path='/',exact=false,strict=false,sensitive=false}=options;
let {keys,regexp} = compilePath(path,{
end:exact,
strict,
sensitive
}); // /post/:id keys=["id"] regexp= /\/post\/([^\/]+?)/
const match = regexp.exec(pathname);
if(!match) return null;
const [url,...values] = match;//['/post/1','1'] url=/post/1 values=['1']
// pathname /post/1/name !== /post/1
const isExact = pathname === url;
//需要精确匹配,但是匹配的不精确,没有完全相等,也相当于没匹配上
if(exact && !isExact) return null;
return { //路由组件中props.match
path,//Route原始path
url,//正则匹配到的浏览器的pathname的部分
isExact,
params:keys.reduce((memo,key,index)=>{
memo[key.name] = values[index];
return memo;
},{})
}
}
export default matchPath;
src\react-router\Router.js
import React from 'react'
import RouterContext from './RouterContext';
class Router extends React.Component{
+ static computeRootMatch(pathname) {
+ return { path: "/", url: "/", params: {}, isExact: pathname === "/" };
+ }
constructor(props){
super(props);
this.state = {
location:props.history.location
}
//当路径发生的变化的时候执行回调
this.unlisten = props.history.listen((location)=>{
this.setState({location});
});
}
componentWillUnmount(){
this.unlisten&&this.unlisten();
}
render(){
let value = {//通过value向下层传递数据
location:this.state.location,
history:this.props.history,
+ match: Router.computeRootMatch(this.state.location.pathname)
}
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
)
}
}
export default Router;
src\react-router\Route.js
import React from 'react'
import RouterContext from './RouterContext';
+import matchPath from './matchPath';
class Route extends React.Component{
static contextType = RouterContext
render(){
const {history,location} = this.context;
const {component:RouteComponent} = this.props;
+ const match = matchPath(location.pathname, this.props);
let routeProps = {history,location};
let element=null;
if (match) {
+ routeProps.match = match;
element = <RouteComponent {...routeProps} />
}
return (
<RouterContext.Provider value={routeProps}>
{element}
</RouterContext.Provider>
)
}
}
export default Route;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
+export {default as matchPath} from './matchPath';
src\react-router\Switch.js
import React from 'react'
import RouterContext from './RouterContext';
import matchPath from './matchPath';
class Switch extends React.Component {
static contextType = RouterContext
render() {
const { location } = this.context;
let element, match;
React.Children.forEach(this.props.children, child => {
if (!match && React.isValidElement(child)) {
element = child;
match = matchPath(location.pathname, child.props);
}
});
return match ? React.cloneElement(element, {computedMatch: match }) : null
}
}
export default Switch;
src\react-router\Route.js
import React from 'react'
import RouterContext from './RouterContext';
import matchPath from './matchPath';
class Route extends React.Component{
static contextType = RouterContext
render(){
const {history,location} = this.context;
+ const {component:RouteComponent,computedMatch} = this.props;
+ const match = computedMatch ? computedMatch : matchPath(location.pathname, this.props);
let routeProps = {history,location};
let element=null;
if (match) {
routeProps.match = match;
element = <RouteComponent {...routeProps} />
}
return (
<RouterContext.Provider value={routeProps}>
{element}
</RouterContext.Provider>
)
}
}
export default Route;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
export {default as matchPath} from './matchPath';
+export {default as Switch} from './Switch';
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Switch} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
+ <Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
+ </Switch>
</Router>
,document.getElementById('root'));
src\react-router\Redirect.js
import React from 'react'
import RouterContext from './RouterContext';
import Lifecycle from './Lifecycle';
function Redirect({to}){
return (
<RouterContext.Consumer>
{
contextValue=>{
const {history}= contextValue;
return (
<Lifecycle
onMount={()=>history.push(to)}
/>
);
}
}
</RouterContext.Consumer>
);
}
export default Redirect;
src\react-router\Lifecycle.js
import React from 'react';
class Lifecycle extends React.Component{
componentDidMount(){
if(this.props.onMount)
this.props.onMount(this);
}
componentWillUnmount(){
if(this.props.onUnmount)
this.props.onUnmount(this);
}
render(){
return null;
}
}
export default Lifecycle;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
export {default as matchPath} from './matchPath';
export {default as Switch} from './Switch';
+export {default as Redirect} from './Redirect';
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import {HashRouter as Router,Route,Switch,Redirect} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
+ <Redirect to="/"/>
</Switch>
</Router>
,document.getElementById('root'));
src\react-router-dom\Link.js
import React from 'react';
import {__RouterContext as RouterContext} from '../react-router';
export default function Link(props){
return (
<RouterContext.Consumer>
{
contextValue=>{
return (
<a
{...props}
onClick={(event)=>{
event.preventDefault();
contextValue.history.push(props.to);
}}
>{props.children}</a>
)
}
}
</RouterContext.Consumer>
)
}
src\react-router-dom\index.js
export * from '../react-router';
export {default as HashRouter} from './HashRouter';
export {default as BrowserRouter} from './BrowserRouter';
+export {default as Link} from './Link';
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import {HashRouter as Router,Route,Switch,Redirect,Link} from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<Router>
+ <ul>
+ <li><Link to="/">首页</Link></li>
+ <li><Link to="/user" >用户管理</Link></li>
+ <li><Link to="/profile" >个人中心</Link></li>
+ </ul>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Route path="/profile" component={Profile}/>
<Redirect to="/"/>
</Switch>
</Router>
,document.getElementById('root'));
src\utils.js
export const UserAPI = {
list(){
let usersStr = localStorage.getItem('users');
let users= usersStr ? JSON.parse(usersStr) : [];
return users;
},
add(user){
let users = UserAPI.list();
users.push(user);
localStorage.setItem('users', JSON.stringify(users));
},
find(id){
let users = UserAPI.list();
return users.find((user) => user.id === id);
}
}
src\components\User.js
import React from 'react';
import { Link, Route } from '../react-router-dom';
+import UserAdd from './UserAdd';
+import UserDetail from './UserDetail';
+import UserList from './UserList';
export default function User() {
return (
+ <div>
+ <ul>
+ <li><Link to="/user/list">用户列表</Link></li>
+ <li><Link to="/user/add">添加用户</Link></li>
+ </ul>
+ <div>
+ <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';
import {UserAPI} from '../utils';
export default class UserAdd extends Component {
usernameRef
constructor(props) {
super(props);
this.usernameRef = React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
let username = this.usernameRef.current.value;
UserAPI.add({ id: Date.now() + '', username });
this.props.history.push('/user/list');
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.usernameRef} />
<button type="submit" >提交</button>
</form>
)
}
}
src\components\UserList.js
import React, { Component } from 'react'
import { Link } from '../react-router-dom';
import {UserAPI} from '../utils';
export default class UserList extends Component {
state = { users: [] }
componentDidMount() {
let users = UserAPI.list();
this.setState({ users });
}
render() {
return (
<ul >
{
this.state.users.map((user, index) => (
<li key={index}>
<Link to={{ pathname: `/user/detail/${user.id}`, state: user }}>{user.username}</Link>
</li>
))
}
</ul>
)
}
}
src\components\UserDetail.js
import React, { Component } from 'react';
import {UserAPI} from '../utils';
export default class UserDetail extends Component {
state = {
user: {}
}
componentDidMount() {
let user = this.props.location.state;
if (!user) {
let id = this.props.match.params.id;
user = UserAPI.find(id);
}
if (user) this.setState({ user });
}
render() {
let user = this.state.user;
return (
<div>
{user.id}:{user.username}
</div>
)
}
}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import {HashRouter as Router,Route,Switch,Redirect,Link} 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';
ReactDOM.render(
<Router>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/user" >用户管理</Link></li>
<li><Link to="/profile" >个人中心</Link></li>
</ul>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
+ <Protected path="/profile" component={Profile}/>
+ <Route path="/login" component={Login}/>
<Redirect to="/"/>
</Switch>
</Router>
,document.getElementById('root'));
src\react-router\Route.js
import React from 'react'
import RouterContext from './RouterContext';
import matchPath from './matchPath';
class Route extends React.Component{
static contextType = RouterContext
render(){
const {history,location} = this.context;
+ const {component:RouteComponent,computedMatch,render} = this.props;
const match = computedMatch ? computedMatch : matchPath(location.pathname, this.props);
let routeProps = {history,location};
let element=null;
+ if (match) {
+ routeProps.match = match;
+ if (RouteComponent) {
+ element = <RouteComponent {...routeProps} />
+ } else if (render) {
+ element= render(routeProps);
+ } else {
+ element= null;
+ }
+ }else {
+ element= null;
+ }
return (
<RouterContext.Provider value={routeProps}>
{element}
</RouterContext.Provider>
)
}
}
export default Route;
src\components\Login.js
import React from 'react';
class Login extends React.Component{
login = ()=>{
localStorage.setItem('login','true');
let to='/';
if(this.props.location.state){
to=this.props.location.state.from||'/';
}
this.props.history.push(to);
}
render(){
return (
<button onClick={this.login}>登录</button>
)
}
}
export default Login;
src\components\Protected.js
import React from 'react';
import {Route,Redirect} from '../react-router-dom';
const Protected = (props)=>{
let {component:RouteComponent,path} = props;
return (
<Route path={path} render={
(routeProps)=>(
localStorage.getItem('login')?<RouteComponent {...routeProps}/>:
<Redirect to={{pathname:'/login',state:{from:path}}}/>
)
}/>
)
}
export default Protected;
public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React App</title>
<style>
+ .strong{
+ font-size: 20px;
+ }
+ .active{
+ background-color: green;
+ }
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import {HashRouter as Router,Route,Switch,Redirect,NavLink} 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';
ReactDOM.render(
<Router>
<ul>
+ <li><NavLink className="strong" style={{textDecoration: 'line-through'}} activeStyle={{color:'red'}} to="/" exact>Home</NavLink></li>
+ <li><NavLink activeStyle={{color:'red'}} to="/user">User</NavLink></li>
+ <li><NavLink activeStyle={{color:'red'}} to="/profile">Profile</NavLink></li>
</ul>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Protected path="/profile" component={Profile}/>
<Route path="/login" component={Login}/>
<Redirect to="/"/>
</Switch>
</Router>
,document.getElementById('root'));
src\react-router-dom\NavLink.js
import React from 'react';
import {Route} from '../react-router';
import matchPath from '../react-router/matchPath';
import Link from './Link';
function NavLink(props){
const {
to:path,//Link指向的路径
className:classNameProp='',//基本的类名
style:styleProp={},//基本的行内样式
activeClassName='active',//激活的类名
activeStyle={},//激活的行内样式
children,//儿子
exact//是否要精确匹配
}= props;
return (
<Route path={path} exact={exact}>
{
({match})=>{
let className = match?joinClassNames(classNameProp,activeClassName):classNameProp;
let style =match?{...styleProp,...activeStyle}:styleProp;
let linkProps = {
className,
style,
to:path,
children
}
return <Link {...linkProps}/>
}
}
</Route>
)
}
function NavLink(props){
let context = React.useContext(RouterContext);
let {location:{pathname}}= context;
const {
to:path,//Link指向的路径
className:classNameProp='',//基本的类名
style:styleProp={},//基本的行内样式
activeClassName='active',//激活的类名
activeStyle={},//激活的行内样式
children,//儿子
exact//是否要精确匹配
}= props;
//pathname浏览器的路径 path来自于NavLink的配置
let isActive = matchPath(pathname,{path,exact});
let className = isActive?joinClassNames(classNameProp,activeClassName):classNameProp;
let style = isActive?{...styleProp,...activeStyle}:styleProp;
let linkProps = {
className,
style,
to:path,
children
}
return <Link {...linkProps}/>;
}
function joinClassNames(...classnames){
return classnames.filter(c=>c).join(' ');
}
export default NavLink;
src\react-router-dom\index.js
export * from '../react-router';
export {default as HashRouter} from './HashRouter';
export {default as BrowserRouter} from './BrowserRouter';
export {default as Link} from './Link';
+export { default as NavLink } from "./NavLink";
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter as Router,Route,Switch,Redirect,NavLink} 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 NavHeader from './components/NavHeader';
ReactDOM.render(
<Router>
<>
+ <NavHeader title="欢迎光临"/>
<ul>
<li><NavLink className="strong" style={{textDecoration: 'line-through'}} activeStyle={{color:'red'}} to="/" exact>Home</NavLink></li>
<li><NavLink activeStyle={{color:'red'}} to="/user">User</NavLink></li>
<li><NavLink activeStyle={{color:'red'}} to="/profile">Profile</NavLink></li>
</ul>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Protected path="/profile" component={Profile}/>
<Route path="/login" component={Login}/>
<Redirect to="/"/>
</Switch>
</>
</Router>
,document.getElementById('root'));
src\components\NavHeader.js
import React from 'react';
import {withRouter} from '../react-router-dom';
class NavHeader extends React.Component{
render(){
return (
<div onClick={()=>this.props.history.push('/')} >{this.props.title}</div>
)
}
}
export default withRouter(NavHeader);
src\react-router\withRouter.js
import React from 'react';
import RouterContext from './RouterContext';
function withRouter(OldComponent){
return props => {
return (
<RouterContext.Consumer>
{
contextValue =>{
return <OldComponent {...props} {...contextValue}/>
}
}
</RouterContext.Consumer>
)
}
}
export default withRouter;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
export {default as matchPath} from './matchPath';
export {default as Switch} from './Switch';
export {default as Redirect} from './Redirect';
+export {default as withRouter} from './withRouter';
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import {BrowserRouter as Router,Route,Switch,Redirect,NavLink} 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 NavHeader from './components/NavHeader';
ReactDOM.render(
+ <Router getUserConfirmation={()=>window.confirm}>
<>
<NavHeader title="欢迎光临"/>
<ul>
<li><NavLink className="strong" style={{textDecoration: 'line-through'}} activeStyle={{color:'red'}} to="/" exact>Home</NavLink></li>
<li><NavLink activeStyle={{color:'red'}} to="/user">User</NavLink></li>
<li><NavLink activeStyle={{color:'red'}} to="/profile">Profile</NavLink></li>
</ul>
<Switch>
<Route path="/" component={Home} exact/>
<Route path="/user" component={User} />
<Protected path="/profile" component={Profile}/>
<Route path="/login" component={Login}/>
<Redirect to="/"/>
</Switch>
</>
</Router>
,document.getElementById('root'));
src\components\UserAdd.js
import React, { Component } from 'react';
import {UserAPI} from '../utils';
import {Prompt} from '../react-router-dom';
export default class UserAdd extends Component {
usernameRef
+ state = {isBlocking:false}
constructor(props) {
super(props);
this.usernameRef = React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
+ this.setState({
+ isBlocking:false
+ },()=>{
let username = this.usernameRef.current.value;
UserAPI.add({ id: Date.now() + '', username });
this.props.history.push('/user/list');
+ });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
+ <Prompt
+ when={this.state.isBlocking}
+ message = {
+ (location)=>`请问你确定要跳转到${location.pathname}吗?`
+ }
+ />
+ <input type="text" ref={this.usernameRef} onChange={(event) => {
+ this.setState({ isBlocking: event.target.value.length > 0 });
+ }} />
<button type="submit" >提交</button>
</form>
)
}
}
src\react-router\Prompt.js
import React from 'react';
import RouterContext from './RouterContext';
import LifeCycle from './LifeCycle';
//函数组件
function Prompt(props){
let value = React.useContext(RouterContext);
React.useEffect(()=>{
return value.history.block(props.message)
});
return null;
}
//类组件
class Prompt extends React.Component{
static contextType = RouterContext;
componentDidMount(){
const block = this.context.history.block;
this.release = block(this.props.message)
}
componentWillUnmount(){
this.release()
}
render(){
return null;
}
}
//源码
function Prompt({when,message}){
return (
<RouterContext.Consumer>
{
(value)=>{
//如果不需要阻止跳转,则可以直接返回null,什么都不做
if(!when)return null;
//需要给history对象上添加一个block方法
const block = value.history.block;
return (
<LifeCycle
onMount={lifeCycleInstance=>lifeCycleInstance.release = block(message)}
onUnMount={lifeCycleInstance=>lifeCycleInstance.release()}
/>
)
}
}
</RouterContext.Consumer>
)
}
export default Prompt;
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
export {default as matchPath} from './matchPath';
export {default as Switch} from './Switch';
export {default as Redirect} from './Redirect';
export {default as withRouter} from './withRouter';
+export {default as Prompt} from './Prompt';
src\history\createBrowserHistory.js
function createBrowserHistory(props){
+ let confirm =props.getUserConfirmation?props.getUserConfirmation():window.confirm;
let globalHistory = window.history;
let listeners = [];
+ let message;
function go(n){
globalHistory.go(n);
}
function goBack(){
go(-1)
}
function goForward(){
go(1)
}
function listen(listener){
listeners.push(listener);
return function(){//unlisten
listeners = listeners.filter(l=>l!==listener);
}
}
function setState(newState){
Object.assign(history,newState);
history.length = globalHistory.length;
listeners.forEach(listener=>listener(history.location));
}
/**
* @param {*} pathname 可能是对象,也可能是字符串
* @param {*} state 这个路径的状态对象是什么,只是一个路径的描述信息,可以放任何
*/
function push(pathname, state){
const action = 'PUSH';//表示发了什么动作引起了路径变化 POP PUSH
if(typeof pathname === 'object'){
state=pathname.state;
pathname= pathname.pathname;
}
+ if(message){
+ let showMessage = message({pathname});
+ let allow = confirm(showMessage);
+ if(!allow){
+ return;
+ }
+ }
globalHistory.pushState(state,null,pathname);
let location = {state,pathname};
setState({action,location});
}
+ function block(newMessage){
+ message=newMessage;
+ return ()=> message=null;
+ }
const history = {
action: "POP",
go,
goBack,
goForward,
listen,
location:{pathname:window.location.pathname,state:globalHistory.state},
push,
+ block
}
return history;
}
export default createBrowserHistory;
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router,Route,Link,
useParams,
useLocation,
useHistory,
useRouteMatch
} from './react-router-dom';
function Home(){
return <div>首页</div>;
}
function UserDetail(){
let params = useParams();
console.log('params',params);
let location = useLocation();
console.log('location',location);
let history = useHistory();
console.log('history',history);
return <div>User id:{params.id} <br/>name:{location.state.name}</div>;
}
function Post(){
let match = useRouteMatch({
path:'/post/:id',
strict:true,
sensitive:true
});
console.log('match',match);
return match?<div>id:{match.params.id}</div>:<div>Not Found</div>
}
ReactDOM.render(
<Router>
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to={{pathname:"/user/detail/1",state:{id:1,name:'珠峰架构'}}}>用户1详情</Link></li>
<li><Link to="/post/1">贴子</Link></li>
</ul>
<Route path="/" component={Home}/>
<Route path="/user/detail/:id" component={UserDetail}/>
<Route path="/post/:id" component={Post}/>
</div>
</Router>,
document.getElementById('root')
);
src\react-router\hooks.js
import React from 'react';
import RouterContext from './RouterContext';
import matchPath from './matchPath';
export function useParams(){
let match = React.useContext(RouterContext).match;
return match?match.params:{};
}
export function useLocation(){
return React.useContext(RouterContext).location;
}
export function useHistory(){
return React.useContext(RouterContext).history;
}
export function useRouteMatch(path){
const location = useLocation();//获取当前的路径pathname代表当前的路径名
let match = React.useContext(RouterContext).match;//获得匹配结果
return path?matchPath(location.pathname,path):match;
}
src\react-router\index.js
export {default as Route} from './Route';
export {default as Router} from './Router';
export {default as __RouterContext} from './RouterContext';
export {default as matchPath} from './matchPath';
export {default as Switch} from './Switch';
export {default as Redirect} from './Redirect';
export {default as withRouter} from './withRouter';
export {default as Prompt} from './Prompt';
+export {useHistory,useLocation,useParams,useRouteMatch} from './hooks';
src\index.js
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router, Route,Link
} from 'react-router-dom';
function lazy(load) {
return class extends React.Component {
state = { InnerComponent: null }
componentDidMount() {
load().then(result => {
this.setState({ InnerComponent: result.default || result });
});
}
render() {
let { InnerComponent } = this.state;
return InnerComponent ? <InnerComponent /> : null;
}
}
}
const LazyHome = React.lazy(() => import(/* webpackChunkName: "Home" */'./components/Home'));
const LazyLogin = React.lazy(() => import(/* webpackChunkName: "Login" */'./components/Login'));
function Loading() {
return <div>加载中......</div>
}
function SuspenseHome() {
return (
<Suspense fallback={<Loading />}>
<LazyHome />
</Suspense>
)
}
function SuspenseLogin() {
return (
<Suspense fallback={<Loading />}>
<LazyLogin />
</Suspense>
)
}
//webpack chunkFilename
ReactDOM.render(
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/login">User</Link></li>
</ul>
<Route exact path="/" component={SuspenseHome} />
<Route path="/login" component={SuspenseLogin} />
</div>
</Router>,
document.getElementById('root')
);