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;
}
</style>
</head>
<body>
<div id="root"></div>
<ul>
<li><a href="#/a">/a</a></li>
<li><a href="#/b">/b</a></li>
</ul>
<script>
window.addEventListener('hashchange',()=>{
console.log(window.location.hash);
let pathname = window.location.hash.slice(1);//把最前面的那个#删除
root.innerHTML = pathname;
});
</script>
</body>
</html>
history.pushState()
和history.replaceState()
,和1个事件window.onpopstate
replaceState
是替换浏览器历史堆栈的当前历史记录为设定的urlreplaceState
不会改动浏览器历史堆栈的当前指针history.forward
、history.back
、和history.go
触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针onpopstate
事件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;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
var historyObj = window.history;
//监听路径改变事件 表示将当前的状态变更了,弹出了
window.onpushstate = (event) => {
console.log(event.type,event.detail.state);
root.innerHTML = window.location.pathname;
}
window.addEventListener('popstate', (event) => {
console.log(event.type,event.state);
root.innerHTML = window.location.pathname;
});
(function (historyObj) {
let oldPushState = history.pushState;//缓存原生的pushState
historyObj.pushState = function (state, title, pathname) {
let result = oldPushState.apply(history, arguments);
if (typeof window.onpushstate === 'function') {
window.onpushstate(new CustomEvent('pushstate',{detail:{pathname,state}}));
}
return result;
}
})(historyObj);
let oldHistoryLength = historyObj.length;
setTimeout(() => {
historyObj.pushState({ page: 1 }, { title: 'page1' }, '/page1');//page1
console.log(historyObj.length-oldHistoryLength);
}, 1000);
setTimeout(() => {
historyObj.pushState({ page: 2 }, { title: 'page2' }, '/page2');//page2
console.log(historyObj.length-oldHistoryLength);
}, 2000);
setTimeout(() => {
historyObj.pushState({ page: 3 }, { title: 'page3' }, '/page3');//page3
console.log(historyObj.length-oldHistoryLength);
}, 3000);
setTimeout(() => {
historyObj.back();//historyObj.go(-1);//page2
setTimeout(()=>console.log(historyObj.length-oldHistoryLength),100);
}, 4000);
setTimeout(() => {
historyObj.pushState({ page:4 }, { title: 'page4' }, '/page4');//page4
console.log(historyObj.length-oldHistoryLength);
}, 5000);
setTimeout(() => {
historyObj.go(1);
console.log(historyObj.length-oldHistoryLength);//page4
}, 6000);
</script>
</body>
</html>
npm i react-router-dom --save
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</HashRouter>
, document.getElementById('root'));
src\components\Home.js
import React from 'react';
function Home(props) {
console.log(props);
return (
<div>Home</div>
)
}
export default Home;
src\components\User.js
import React from 'react';
function User() {
return (
<div>User</div>
)
}
export default User;
src\components\Profile.js
import React from 'react';
function Profile() {
return (
<div>Profile</div>
)
}
export default Profile;
src\react-router-dom\index.js
import React from 'react'
import { Router } from '../react-router';
import { createHashHistory, createBrowserHistory } from "history";
export * from '../react-router';
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({});
export {
NavigationContext,
LocationContext,
RouteContext
};
export function Router({ children, location, navigator }) {
let navigationContext = React.useMemo(
() => ({ navigator }),
[navigator]
);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location }}
/>
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();//当前的路径对象
let pathname = location.pathname || "/";//当前的路径
for (let i = 0; i < routes.length; i++) {
let { path, element } = routes[i];
let match = matchPath(path, pathname);
if (match) {
return element;
}
}
return null;
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, element => {
let route = {
path: element.props.path,
element: element.props.element
};
routes.push(route);
});
return routes;
}
export function Route(props) {
}
function compilePath(path) {
let regexpSource = "^" + path;
regexpSource += "$";
let matcher = new RegExp(regexpSource);
return matcher;
}
export function matchPath(path, pathname) {
let matcher = compilePath(path);
let match = pathname.match(matcher);
if (!match) return null;
return match;
}
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',()=>{
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({ location: history.location }));
}
function push(pathname,nextState){
const action = 'PUSH';//action表示是由于什么样的动作引起了路径的变更
if(typeof pathname === 'object'){
state = pathname.state;
pathname = pathname.pathname;
}else{
state=nextState;
}
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({ location: 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\index.js
import React from 'react'
import { Router } from '../react-router';
+import { createHashHistory, createBrowserHistory } from "../history";
export * from '../react-router';
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
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 } ]
**/
表达式 | 含义 |
---|---|
() | 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容) |
(?:) | 表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来 |
(? |
表示命名捕获分组,反向引用一个命名分组的语法是 \k |
//分组获取
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);
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, BrowserRouter, Routes, Route } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
ReactDOM.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
+ <Route path="/post/:id" element={<Post />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({});
export {
NavigationContext,
LocationContext,
RouteContext
};
export function Router({ children, location, navigator }) {
let navigationContext = React.useMemo(
() => ({ navigator }),
[navigator]
);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location }}
/>
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();//当前的路径对象
let pathname = location.pathname || "/";//当前的路径
for (let i = 0; i < routes.length; i++) {
let { path, element } = routes[i];
let match = matchPath(path, pathname);
if (match) {
+ return React.cloneElement(element, { ...element.props, match });
}
}
return null;
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, element => {
let route = {
path: element.props.path,
element: element.props.element
};
routes.push(route);
});
return routes;
}
export function Route(props) {
}
function compilePath(path) {
+ let paramNames = [];
+ let regexpSource = "^" + path
+ .replace(/:(\w+)/g, (_, key) => {
+ paramNames.push(key);
+ return "([^\\/]+)";
+ });
+ regexpSource += "$";
let matcher = new RegExp(regexpSource);
+ return [matcher, paramNames];
}
export function matchPath(path, pathname) {
+ let [matcher, paramNames] = compilePath(path);
let match = pathname.match(matcher);
if (!match) return null;
+ let matchedPathname = match[0];
+ let values = match.slice(1);
+ let params = paramNames.reduce(
+ (memo, paramName, index) => {
+ memo[paramName] = values[index];
+ return memo;
+ },
+ {}
+ );
+ return { params, pathname: matchedPathname, path };
}
src\components\Post.js
import React from 'react';
function Post(props) {
console.log(props);
return (
<div>Post</div>
)
}
export default Post;
import React from 'react';
import ReactDOM from 'react-dom';
+import { HashRouter, BrowserRouter, Routes, Route, Link } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
ReactDOM.render(
<BrowserRouter>
+ <ul>
+ <li><Link to="/">首页</Link></li>
+ <li><Link to="/user" >用户管理</Link></li>
+ <li><Link to="/profile" >个人中心</Link></li>
+ </ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
<Route path="/post/:id" element={<Post />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\react-router-dom\index.js
import React from 'react'
+import { Router, useNavigate } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../history";
export * from '../react-router';
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
+export function Link({ to, ...rest }) {
+ let navigate = useNavigate();
+ function handleClick() {
+ navigate(to);
+ }
+ return (
+ <a
+ {...rest}
+ href={to}
+ onClick={handleClick}
+ />
+ );
+}
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({});
export {
NavigationContext,
LocationContext,
RouteContext
};
export function Router({ children, location, navigator }) {
let navigationContext = React.useMemo(
() => ({ navigator }),
[navigator]
);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location }}
/>
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();//当前的路径对象
let pathname = location.pathname || "/";//当前的路径
for (let i = 0; i < routes.length; i++) {
let { path, element } = routes[i];
let match = matchPath(path, pathname);
if (match) {
return React.cloneElement(element, { ...element.props, match });
}
}
return null;
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, element => {
let route = {
path: element.props.path,
element: element.props.element
};
routes.push(route);
});
return routes;
}
export function Route(props) {
}
function compilePath(path) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/:(\w+)/g, (_, key) => {
paramNames.push(key);
return "([^\\/]+)";
});
regexpSource += "$";
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
export function matchPath(path, pathname) {
let [matcher, paramNames] = compilePath(path);
let match = pathname.match(matcher);
if (!match) return null;
let matchedPathname = match[0];
let values = match.slice(1);
let params = paramNames.reduce(
(memo, paramName, index) => {
memo[paramName] = values[index];
return memo;
},
{}
);
return { params, pathname: matchedPathname, path };
}
+export function useNavigate() {
+ let { navigator } = React.useContext(NavigationContext);
+ let navigate = React.useCallback((to) => {
+ navigator.push(to);
+ }, [navigator]);
+ return navigate;
+}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import { BrowserRouter, Routes, Route, Link } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
+import UserAdd from './components/UserAdd';
+import UserList from './components/UserList';
+import UserDetail from './components/UserDetail';
import Profile from './components/Profile';
import Post from './components/Post';
ReactDOM.render(
<BrowserRouter>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/user" >用户管理</Link></li>
<li><Link to="/profile" >个人中心</Link></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
+ <Route path="/user/*" element={<User />} >
+ <Route path="add" element={<UserAdd />} />
+ <Route path="list" element={<UserList />} />
+ <Route path="detail/:id" element={<UserDetail />} />
+ </Route>
<Route path="/profile" element={<Profile />} />
<Route path="/post/:id" element={<Post />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\components\User.js
import React from 'react';
+import { Link, Outlet } from '../react-router-dom';
function User() {
return (
+ <div>
+ <ul>
+ <li><Link to="/user/list">用户列表</Link></li>
+ <li><Link to="/user/add">添加用户</Link></li>
+ </ul>
+ <div>
+ <Outlet />
+ </div>
+ </div>
)
}
export default User;
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\UserAdd.js
import React from 'react';
import { useNavigate } from '../react-router-dom';
import { UserAPI } from '../utils';
function UserAdd(props) {
const usernameRef = React.useRef();
const navigate = useNavigate();
const handleSubmit = (event) => {
event.preventDefault();
let username = usernameRef.current.value;
UserAPI.add({ id: Date.now() + '', username });
navigate('/user/list');
}
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={usernameRef} />
<button type="submit" >提交</button>
</form>
)
}
export default UserAdd;
src\components\UserDetail.js
import React from 'react';
import { UserAPI } from '../utils';
function UserList() {
const [user, setUser] = React.useState({});
React.useEffect(() => {
let user = this.props.location.state;
if (!user) {
let id = this.props.match.params.id;
user = UserAPI.find(id);
}
if (user) setUser({ user });
}, []);
return (
<div>
{user.id}:{user.username}
</div>
)
}
export default UserList;
src\components\UserList.js
import React from 'react'
import { Link } from '../react-router-dom';
import { UserAPI } from '../utils';
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
let users = UserAPI.list();
setUsers(users);
}, []);
return (
<ul >
{
users.map((user, index) => (
<li key={index}>
<Link to={{ pathname: `/user/detail/${user.id}`, state: user }}>{user.username}</Link>
</li>
))
}
</ul>
)
}
export default UserList;
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
+//路由上下文
+const RouteContext = React.createContext({
+ outlet: null,
+ matches: []
+});
+const OutletContext = React.createContext();
+const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
+export function Outlet(props) {
+ return useOutlet(props.context);
+}
+export function useOutlet(context) {
+ let outlet = React.useContext(RouteContext).outlet;
+ if (outlet) {
+ return <OutletContext.Provider value={context} children={outlet} />;
+ }
+ return outlet;
+}
export function Router({ children, location, navigator }) {
let navigationContext = React.useMemo(
() => ({ navigator }),
[navigator]
);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location }}
/>
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
+export function useParams() {
+ let {matches} = React.useContext(RouteContext);
+ let routeMatch = matches[matches.length - 1];
+ return routeMatch ? routeMatch.params : {};
+}
export function useRoutes(routes) {
+ //当前的路径对象
+ let location = useLocation();
+ //当前的路径字符串
+ let pathname = location.pathname || "/";
+ //匹配路径
+ let matches = matchRoutes(routes, { pathname });
+ //渲染匹配的结果
+ return _renderMatches(matches);
}
+function _renderMatches(matches, parentMatches = []) {
+ if (matches == null) return null;
+ return matches.reduceRight((outlet, match, index) => {
+ return (
+ <RouteContext.Provider children={match.route.element || <Outlet />} value={{
+ outlet,
+ matches: parentMatches.concat(matches.slice(0, index + 1))
+ }} />
+ );
+ }, null);
+}
+export function matchRoutes(routes, location) {
+ //获取路径
+ let pathname = location.pathname || "/";
+ //打平所有的分支
+ let branches = flattenRoutes(routes);
+ rankRouteBranches(branches);
+ //匹配路由分支
+ let matches = null;
+ for (let i = 0; matches == null && i < branches.length; ++i) {
+ matches = matchRouteBranch(branches[i], pathname);
+ }
+ return matches;
+}
+const paramRe = /^:\w+$/;
+const dynamicSegmentValue = 3;
+const indexRouteValue = 2;
+const emptySegmentValue = 1;
+const staticSegmentValue = 10;
+const splatPenalty = -2;
+
+const isSplat = s => s === "*";
+
+function computeScore(path, index) {
+ let segments = path.split("/");
+ let initialScore = segments.length;
+
+ if (segments.some(isSplat)) {
+ initialScore += splatPenalty;
+ }
+
+ if (index) {
+ initialScore += indexRouteValue;
+ }
+ return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
+}
+function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
+ routes.forEach((route) => {
+ let meta = {
+ relativePath: route.path || "",//路由的相对路径
+ route //路由对象
+ };
+ //父路径和子路径拼在一起成为当前的路径
+ let path = joinPaths([parentPath, meta.relativePath]);
+ //各父Meta数组中添加当前的meta
+ let routesMeta = parentsMeta.concat(meta);
+ if (route.children && route.children.length > 0) {
+ flattenRoutes(route.children, branches, routesMeta, path);
+ }
+ branches.push({
+ path,//此配置的完整路径
+ score: computeScore(path, route.index), rankRouteBranches(branches);
+ routesMeta //此配置的meta数据
+ });
+ });
+ return branches;
+}
+function rankRouteBranches(branches) {
+ branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
+}
+function compareIndexes(a, b) {
+ let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
+ return siblings ? a[a.length - 1] - b[b.length - 1] : 0;
+}
+/**
+ * 匹配路由的分支
+ * @param {*} branch /user/星/add
+ * @param {*} pathname
+ * @returns
+ */
+function matchRouteBranch(branch, pathname) {
+ let { routesMeta } = branch;
+ let matchedParams = {};
+ let matchedPathname = "/";
+ let matches = [];
+ for (let i = 0; i < routesMeta.length; ++i) {
+ //获取当前的meta
+ let meta = routesMeta[i];
+ //是否是最后一个
+ let end = i === routesMeta.length - 1;
+ //获取剩下的路径
+ let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
+ //判断路径是否匹配
+ let match = matchPath({
+ path: meta.relativePath,
+ end
+ }, remainingPathname);
+ if (!match) return null;
+ Object.assign(matchedParams, match.params);
+ let route = meta.route;
+ matches.push({
+ params: matchedParams,
+ pathname: joinPaths([matchedPathname, match.pathname]),
+ pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
+ route
+ });
+ if (match.pathnameBase !== "/") {
+ matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
+ }
+ }
+ return matches;
+}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, element => {
let route = {
path: element.props.path,
element: element.props.element
};
+ if (element.props.children) {
+ route.children = createRoutesFromChildren(element.props.children);
+ }
routes.push(route);
});
return routes;
}
export function Route(props) {
}
function compilePath(path, end) {
let paramNames = [];
let regexpSource = "^" + path
+ .replace(/\/*\*?$/, "")
+ .replace(/^\/*/, "/")
.replace(/:(\w+)/g, (_, key) => {
paramNames.push(key);
return "([^\\/]+)";
});
+ if (path.endsWith("*")) {
+ paramNames.push("*");
+ regexpSource += "(?:\\/(.+)|\\/*)$";
+ } else {
+ regexpSource += end ? "\\/*$" : "(?:\\b|\\/|$)";
+ }
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
+export function matchPath(pattern, pathname) {
+ let [matcher, paramNames] = compilePath(pattern.path, pattern.end);
+ //获取匹配结果
+ let match = pathname.match(matcher);
+ if (!match) return null;
+ //获取匹配的路径
+ let matchedPathname = match[0];
+ //只要当前的路径
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
+ //获取后面的分组的值
+ let values = match.slice(1);
+ //获取捕获的分组
+ let captureGroups = match.slice(1);
+ //遍历参数名称
+ let params = paramNames.reduce(
+ (memo, paramName, index) => {
+ if (paramName === "*") {
+ let splatValue = captureGroups[index] || "";
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
+ }
+ memo[paramName] = values[index];
+ return memo;
+ },
+ {}
+ );
+ return {
+ params,
+ pathname: matchedPathname,
+ pathnameBase,
+ pattern
+ };
+}
export function useNavigate() {
let { navigator } = React.useContext(NavigationContext);
let navigate = React.useCallback((to) => {
navigator.push(to);
}, [navigator]);
return navigate;
}
pathnameBase
let pathname = '/user/*';
let matcher = /^\/user(?:\/(.+)|\/*)$/i;
let match = pathname.match(matcher);
let matchedPathname = match[0];
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
let captureGroups = match.slice(1);
let paramNames = ["*"];
let params = paramNames.reduce((memo, paramName, index) => {
if (paramName === "*") {
let splatValue = captureGroups[index];//add
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
}
memo[paramName] = captureGroups[index];
return memo;
}, {});
console.log(params);
console.log(pathnameBase);
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>
+ .active {
+ text-decoration: line-through;
+ }
+ </style>
</head>
<body>
<div id="root"></div>
</body>
</html>
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import { BrowserRouter, Routes, Route, NavLink } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import Profile from './components/Profile';
import Post from './components/Post';
+const activeStyle = { backgroundColor: "red" };
+const activeClassName = 'active';
ReactDOM.render(
<BrowserRouter>
<ul>
+ <li>
+ <NavLink to="/"
+ style={({ isActive }) => isActive ? activeStyle : {}}
+ className={({ isActive }) => isActive ? activeClassName : ''}
+ >首页</NavLink>
+ </li>
+ <li><NavLink to="/user" >用户管理</NavLink></li>
+ <li><NavLink to="/profile" >个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/*" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
<Route path="/profile" element={<Profile />} />
<Route path="/post/:id" element={<Post />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\react-router-dom\index.js
import React from 'react'
+import { Router, useNavigate, useLocation } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../history";
export * from '../react-router';
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export function Link({ to, ...rest }) {
let navigate = useNavigate();
function handleClick() {
navigate(to);
}
return (
<a
{...rest}
href={to}
onClick={handleClick}
/>
);
}
+export const NavLink = function ({
+ className: classNameProp = "",
+ end = false,
+ style: styleProp = {},
+ to,
+ children,
+ ...rest
+}) {
+ let location = useLocation();
+ let path = { pathname: to };
+ let locationPathname = location.pathname;
+ let toPathname = path.pathname;
+ let isActive = locationPathname === toPathname || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === "/");
+ let className;
+ if (typeof classNameProp === "function") {
+ debugger
+ className = classNameProp({
+ isActive
+ });
+ } else {
+ className = [classNameProp, isActive ? "active" : null].filter(Boolean).join(" ");
+ }
+ let style = typeof styleProp === "function" ? styleProp({
+ isActive
+ }) : styleProp;
+ return (
+ <Link {...rest} className={className} style={style} to={to}>
+ {children}
+ </Link>
+ );
+}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
+import { BrowserRouter, Routes, Route, NavLink, Navigate } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import Profile from './components/Profile';
import Post from './components/Post';
const activeStyle = { backgroundColor: "red" };
const activeClassName = 'active';
ReactDOM.render(
<BrowserRouter>
<ul>
<li>
<NavLink to="/"
style={({ isActive }) => isActive ? activeStyle : {}}
className={({ isActive }) => isActive ? activeClassName : ''}
>首页</NavLink>
</li>
<li><NavLink to="/user" >用户管理</NavLink></li>
<li><NavLink to="/profile" >个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/*" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
<Route path="/profile" element={<Profile />} />
<Route path="/post/:id" element={<Post />} />
+ <Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\components\Home.js
import React from 'react';
+import { useNavigate } from '../react-router-dom';
function Home(props) {
+ let navigate = useNavigate();
+ function navigateTo() {
+ navigate('/profile');
+ };
return (
<div>
<p>Home</p>
+ <button onClick={navigateTo}>跳转到/profile</button>
</div>
)
}
export default Home;
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({
outlet: null,
matches: []
});
const OutletContext = React.createContext();
const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
export function Outlet(props) {
return useOutlet(props.context);
}
export function useOutlet(context) {
let outlet = React.useContext(RouteContext).outlet;
if (outlet) {
return <OutletContext.Provider value={context} children={outlet} />;
}
return outlet;
}
export function Router({ children, location, navigator }) {
let navigationContext = React.useMemo(
() => ({ navigator }),
[navigator]
);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
children={children}
value={{ location }}
/>
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
//当前的路径对象
let location = useLocation();
//当前的路径字符串
let pathname = location.pathname || "/";
//匹配路径
let matches = matchRoutes(routes, { pathname });
//渲染匹配的结果
return _renderMatches(matches);
}
function _renderMatches(matches, parentMatches = []) {
if (matches == null) return null;
return matches.reduceRight((outlet, match, index) => {
return (
<RouteContext.Provider children={match.route.element || <Outlet />} value={{
outlet,
matches: parentMatches.concat(matches.slice(0, index + 1))
}} />
);
}, null);
}
export function matchRoutes(routes, location) {
//获取路径
let pathname = location.pathname || "/";
//打平所有的分支
let branches = flattenRoutes(routes);
//匹配路由分支
let matches = null;
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
routes.forEach((route) => {
let meta = {
relativePath: route.path || "",//路由的相对路径
route //路由对象
};
//父路径和子路径拼在一起成为当前的路径
let path = joinPaths([parentPath, meta.relativePath]);
//各父Meta数组中添加当前的meta
let routesMeta = parentsMeta.concat(meta);
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
branches.push({
path,//此配置的完整路径
routesMeta //此配置的meta数据
});
});
return branches;
}
/**
* 匹配路由的分支
* @param {*} branch /user/星/add
* @param {*} pathname
* @returns
*/
function matchRouteBranch(branch, pathname) {
let { routesMeta } = branch;
let matchedParams = {};
let matchedPathname = "/";
let matches = [];
for (let i = 0; i < routesMeta.length; ++i) {
//获取当前的meta
let meta = routesMeta[i];
//是否是最后一个
let end = i === routesMeta.length - 1;
//获取剩下的路径
let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
//判断路径是否匹配
let match = matchPath({
path: meta.relativePath,
end
}, remainingPathname);
if (!match) return null;
Object.assign(matchedParams, match.params);
let route = meta.route;
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
route
});
if (match.pathnameBase !== "/") {
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
}
return matches;
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, element => {
let route = {
path: element.props.path,
element: element.props.element
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
export function Route(props) {
}
function compilePath(path, end) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/^\/*/, "/")
.replace(/:(\w+)/g, (_, key) => {
paramNames.push(key);
return "([^\\/]+)";
});
if (path.endsWith("*")) {
paramNames.push("*");
regexpSource += "(?:\\/(.+)|\\/*)$";
} else {
regexpSource += end ? "\\/*$" : "(?:\\b|\\/|$)";
}
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
export function matchPath(pattern, pathname) {
let [matcher, paramNames] = compilePath(pattern.path, pattern.end);
//获取匹配结果
let match = pathname.match(matcher);
if (!match) return null;
//获取匹配的路径
let matchedPathname = match[0];
//只要当前的路径
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
//获取后面的分组的值
let values = match.slice(1);
//获取捕获的分组
let captureGroups = match.slice(1);
//遍历参数名称
let params = paramNames.reduce(
(memo, paramName, index) => {
if (paramName === "*") {
let splatValue = captureGroups[index] || "";
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
}
memo[paramName] = values[index];
return memo;
},
{}
);
return {
params,
pathname: matchedPathname,
pathnameBase,
pattern
};
}
export function useNavigate() {
let { navigator } = React.useContext(NavigationContext);
let navigate = React.useCallback((to) => {
navigator.push(to);
}, [navigator]);
return navigate;
}
+export function Navigate({ to }) {
+ let navigate = useNavigate();
+ React.useEffect(() => {
+ debugger
+ navigate(to);
+ });
+ return null;
+}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Routes, Route, NavLink, Navigate } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import Profile from './components/Profile';
import Post from './components/Post';
+import Protected from './components/Protected';
+import Login from './components/Login';
const activeStyle = { backgroundColor: "red" };
const activeClassName = 'active';
ReactDOM.render(
<BrowserRouter>
<ul>
<li>
<NavLink to="/"
style={({ isActive }) => isActive ? activeStyle : {}}
className={({ isActive }) => isActive ? activeClassName : ''}
>首页</NavLink>
</li>
<li><NavLink to="/user" >用户管理</NavLink></li>
<li><NavLink to="/profile" >个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/*" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
<Route path="/post/:id" element={<Post />} />
+ <Route path="/profile" element={<Protected component={Profile} />} />
+ <Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>
, document.getElementById('root'));
src\components\Login.js
import React from 'react';
import { useNavigate, useLocation } from '../react-router-dom';
function Login() {
let navigation = useNavigate();
let location = useLocation();
const login = () => {
localStorage.setItem('login', 'true');
let to = '/';
if (location.state) {
to = location.state.from || '/';
}
navigation(to);
}
return (
<button onClick={login}>登录</button>
)
}
export default Login;
src\components\Protected.js
import React from 'react';
import { Navigate } from '../react-router-dom';
function Protected(props) {
let { component: RouteComponent, path } = props;
return localStorage.getItem('login') ? <RouteComponent /> :
<Navigate to={{ pathname: '/login', state: { from: path } }} />
}
export default Protected;
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Routes, Route, NavLink, Navigate, useRoutes } from './react-router-dom';
-import Home from './components/Home';
-import User from './components/User';
-import UserAdd from './components/UserAdd';
-import UserList from './components/UserList';
-import UserDetail from './components/UserDetail';
-import Profile from './components/Profile';
-import Post from './components/Post';
-import Protected from './components/Protected';
-import Login from './components/Login';
+import routesConfig from './routesConfig';
+const activeStyle = { backgroundColor: "red" };
+const activeClassName = 'active';
+const LazyPost = React.lazy(() => import('./components/Post'));
+function App() {
+ let [routes, setRoutes] = React.useState(routesConfig);
+ const addRoute = () => {
+ setRoutes([...routes, {
+ path: '/foo', element: (
+ <React.Suspense fallback={<div>loading...</div>}>
+ <LazyPost />
+ </React.Suspense>
+ )
+ }]);
+ }
+ return (
+ <div>
+ {useRoutes(routes)}
+ <button onClick={addRoute}>addRoute</button>
+ </div>
+ )
+}
ReactDOM.render(
<BrowserRouter>
<ul>
<li>
<NavLink to="/"
style={({ isActive }) => isActive ? activeStyle : {}}
className={({ isActive }) => isActive ? activeClassName : ''}
>首页</NavLink>
</li>
<li><NavLink to="/user" >用户管理</NavLink></li>
<li><NavLink to="/profile" >个人中心</NavLink></li>
</ul>
+ <App />
</BrowserRouter>
, document.getElementById('root'));
src\routesConfig.js
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import UserAdd from './components/UserAdd';
import UserDetail from './components/UserDetail';
import UserList from './components/UserList';
import NotFound from './components/NotFound';
import Login from './components/Login';
import Protected from './components/Protected';
const routes = [
{ path: '/', element: <Home /> },
{ path: '/profile', element: <Profile /> },
{
path: 'user',
element: <User />,
children: [
{ path: 'add', element: <UserAdd /> },
{ path: 'list', element: <UserList /> },
{ path: 'detail/:id', element: <UserDetail /> }
]
},
{ path: '/profile', element: <Protected component={Profile} /> },
{ path: '/login', element: <Login /> },
{ path: '*', element: <NotFound /> }
];
export default routes;
src\components\NotFound.js
import React from 'react';
function NotFound(props) {
return (
<div>NotFound</div>
)
}
export default NotFound;