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 http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hash路由</title>
</head>
<body>
<div id="root"></div>
<ul>
<li><a onclick="go('/a')">/a</a></li>
<li><a onclick="go('/b')">/b</a></li>
<li><a onclick="go('/c')">/c</a></li>
<li><a onclick="forward()">前进</a></li>
<li><a onclick="back()">后退</a></li>
</ul>
<script>
function render() {
root.innerHTML = window.location.pathname;
}
//只有当你前进后退的时候会触发,pushState不会触发
window.onpopstate = render;
let historyObj = window.history;
let oldPushState = historyObj.pushState;
historyObj.pushState = function (state, title, url) {
oldPushState.apply(history, arguments);
render();
}
function go(path) {
historyObj.pushState({}, null, path);
}
function forward() {
historyObj.go(1);
//historyObj.forward();
}
function back(path) {
historyObj.go(-1);
//historyObj.back();
}
</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({});
export {
NavigationContext,
LocationContext
};
export function Router({ children, location, navigator }) {
const navigationContext = React.useMemo(() => ({ navigator }), [navigator]);
const locationContext = React.useMemo(() => ({ location }), [location]);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider value={locationContext} children={children} />
</NavigationContext.Provider>
);
}
export function Routes({ children }) {
return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useSearchParams() {
const location = React.useContext(LocationContext).location;
const pathname = location.pathname;
return new URLSearchParams(pathname.split('?')[1]);
}
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:window.location.hash ? window.location.hash.slice(1) : '/',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('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({});
export {
NavigationContext,
LocationContext,
};
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);
return 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) {
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, children }) {
+ const navigate = useNavigate();//是一个跳转路径的方法
+ return (
+ <a href={to} onClick={(event) => {
+ event.preventDefault();
+ navigate(to);
+ }} >{children}</a>
+ )
+}
src\react-router\index.js
import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
export {
NavigationContext,
LocationContext
};
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;
+}
[
{
"path": "/user/*",
"element": "element",
"children": [
{
"path": "add",
"element": "element"
}
]
}
]
{
{
"path": "/user/*",
"routesMeta": [
{
"relativePath": "/user/*",
"route": {
"path": "/user/*",
"element": "element",
"children": "children"
}
}
]
},
{
"path": "/user/*/add",
"routesMeta": [
{
"relativePath": "/user/*",
"route": {
"path": "/user/*",
"element": "element",
"children": "children"
}
},
{
"relativePath": "add",
"route": {
"path": "add",
"element": "element"
}
}
]
}
]
const branches=[
{path:'/user/*/add',routesMeta:[user*Meta,addMeta]},
{path:'/user/*/list',routesMeta:[user*Meta,listMeta]},
{path:'/user/*/detail',routesMeta:[user*Meta,detailMeta]},
{path:'/user/*',routesMeta:[user*Meta]},
]
function flattenRoutes(routes, branches = []) {
routes.forEach(route => {
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches);
}
branches.push({ name: route.name });
});
return branches;
}
let routes = [
{
name: 'A',
children: [
{
name: 'B',
children: [
{
name: 'C'
}
]
}
]
}
]
console.log(flattenRoutes(routes));
let pathname = '/user/add';
let meta = '/user';
let remainingPathname = pathname.slice(meta.length);///add
let str = '/user///add';
str = str.replace(/\/\/+/g, '/');
console.log(str);
let paramNames = [];
let path = '/user';
let end = false;
let regexpSource = "^" + path
.replace(/\/*\*?$/, '') // 把结尾 的 /*替换 为空 /user* /user/* /user//* /user//**
.replace(/^\/*/, '/') //把开始的任意多个星转成一个/ //add=>/add
.replace(/:(\w+)/g, (_, key) => {
paramNames.push(key);
return '([^\\/]+)';
})
if (path.endsWith('*')) {// /user/*
paramNames.push('*');//代表后面的内容可以是任意多个/也可以是/任意内容
regexpSource += "(?:\\/(.+)|\\/*)$";
} else {
regexpSource += end ? "\\/*$" : "(?:\\b|\\/|$)";
}
let matcher = new RegExp(regexpSource);
console.log(matcher);
console.log(paramNames);
let pathname = '/user/add';
let match = pathname.match(matcher);
console.log(match);
//^\/user\/[^\/]+(?:\/(.+)|\/*)$
let matchedPathname = '/user/add/'
console.log('matchedPathname', matchedPathname);
//其实就是把结尾的/去掉
//(.)就是第1个分组
let pathnameBase = matchedPathname.replace(/(.)\/+$/, '$1');
console.log('pathnameBase', pathnameBase);
// /user/add/xx/yy
// * /add/xx/yy
pathnameBase
let path = '/user/*';
let pathname = '/user/100/detail';
let pathRegexp = /^\/user(?:\/(.+)|\/*)$/;
let match = pathname.match(pathRegexp);
console.log(match);
let matchedPathname = match[0];
let starValue = match[1];
console.log('starValue', starValue);// /user/add
console.log('matchedPathname', matchedPathname);// /user/add
console.log('matchedPathname.length', matchedPathname.length);//9
console.log('value.length', starValue.length);//3
let pathnameBase = matchedPathname.slice(0, matchedPathname.length - starValue.length)
pathnameBase = matchedPathname.slice(0, 6);// /user/
pathnameBase = pathnameBase.replace(/(.)\/+$/, '$1');
console.log('pathnameBase', pathnameBase);
// pathnameBase /user
// /user/add
//如果路径中有*就减少2
const splatPenalty = -2;
const indexRouteValue = 2;
const paramRegexp = /^:\w+$/;
const dynamicSegmentValue = 3;
const emptySegmentValue = 1;
const staticSegmentValue = 10;
const isSplat = s => s === '*';//console.log(computeScore('/user/*', 1));
function computeScore(path, index) {
// /user/add
let segments = path.split('/');//['','user','*']//3
let initialScore = segments.length;//3
if (segments.some(isSplat)) {
initialScore += splatPenalty;//1
}
if (typeof index !== 'undefined') {
initialScore += indexRouteValue;//3
}
//['','user','*']=>['','user']
return segments.filter(s => !isSplat(s)).reduce((score, segment) => {
let currentScope = 0;
//如果这个片断是路径参数的话 :id
if (paramRegexp.test(segment)) {
currentScope += dynamicSegmentValue;
} else {
if (segment === '') {
currentScope += emptySegmentValue;
} else {
currentScope += staticSegmentValue;
}
}
score += currentScope;
return score;
}, initialScore);
}
console.log(computeScore('/user/*', 1));
function compareIndexes(a, b) {
//如果a的长度和B的长度是一样的话,并且a的每一项和b的每一项都相同的话,说明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;
}
console.log(compareIndexes([1, 1, 1], [1, 1, 2]));
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';
export default function UserAdd() {
const navigate = useNavigate();
const nameRef = React.useRef();
const handleSubmit = (event) => {
event.preventDefault();
let name = nameRef.current.value;
UserAPI.add({ id: Date.now() + "", name });
navigate('/user/list');
}
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} />
<button type="submit">添加</button>
</form>
)
}
src\components\UserDetail.js
import React from 'react'
import { useLocation, useParams } from 'react-router-dom';
import { UserAPI } from '../utils';
export default function UserDetail(props) {
const location = useLocation();
const { id } = useParams();
const [user, setUser] = React.useState({});
React.useEffect(() => {
let user = location.state;//首先取路径对象的状态
if (!user) {
if (id) {
user = UserAPI.find(id);
}
}
if (user) setUser(user);
}, []);
return (
<div>
{user.id}:{user.name}
</div>
)
}
src\components\UserList.js
import React from 'react'
import { Link } from 'react-router-dom';
import { UserAPI } from '../utils';
export default function User() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
let users = UserAPI.list();
setUsers(users);
}, []);
return (
<ul>
{
users.map((user, index) => (
<li key={index}>
<Link to={`/user/detail/${user.id}`} state={user}>{user.name}</Link>
</li>
))
}
</ul>
)
}
src\react-router\index.js
import React, { memo } from 'react';
//导航上下文
const NavigationContext = React.createContext();
//路径上下文
const LocationContext = React.createContext();
+//路由上下文
+const RouteContext = React.createContext();
+export { NavigationContext, LocationContext, RouteContext }
+export function Outlet() {
+ return useOutlet();
+}
+function useOutlet() {
+ let { outlet } = React.useContext(RouteContext);
+ return outlet;
+}
+export function useParams() {
+ let { matches } = React.useContext(RouteContext);
+ let routeMatch = matches[matches.length - 1];
+ return routeMatch ? routeMatch.params : {};
+}
/**
*
* @param {*} children 子组件
* @param {*} location 当前的路径对象
* @param {*} navigator history对象 go back forward push....
*/
export function Router({ children, location, navigator }) {
const navigationContext = React.useMemo(() => ({ navigator }), [navigator]);
const locationContext = React.useMemo(() => ({ location }), [location]);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider value={locationContext} children={children} />
</NavigationContext.Provider>
)
}
export function Routes({ children }) {
const routes = createRoutesFromChildren(children);
return useRoutes(routes)
}
//
export function Route() { }
export function useLocation() {
return React.useContext(LocationContext).location;
}
/**
* 把此路由配置数组渲染成真正的组件
* @param {*} routes 路由配置数组
*/
export function useRoutes(routes) {
+ //当前的路径对象
+ let location = useLocation();
+ //当前的路径字符串 /user/add
+ let pathname = location.pathname;
+ //用当前的地址栏中的路径和路由进行匹配
+ let matches = matchRoutes(routes, { pathname });
+ //渲染匹配的结果
+ return _renderMatches(matches);
}
+function _renderMatches(matches) {
+ if (!matches) return null;
+ //渲染结果的时候是从右向左执行的
+ //matches=[{route:{element:User}},{route:{element:UserAdd]}}]
+ return matches.reduceRight((outlet, match, index) => {
+ return (
+ <RouteContext.Provider value={{ outlet, matches: matches.slice(0, index + 1) }}>
+ {match.route.element}
+ </RouteContext.Provider>
+ )
+ }, null);
+}
/**
* 用当前路径和路由配置进行匹配,获取匹配的结果
* @param {*} routes 路由配置
* @param {*} location 当前路径
*/
+function matchRoutes(routes, location) {
+ //获取路径名
+ let pathname = location.pathname;
+ //打平所有的分支路径
+ let branches = flattenRoutes(routes);
+ rankRouteBranches(branches);
+ console.log(branches);
+ //匹配的结果
+ let matches = null;
+ //按分支顺序依次进行匹配,如果匹配上了,直接退出循环,不再进行后续的匹配
+ for (let i = 0; matches == null && i < branches.length; i++) {
+ matches = matchRouteBranch(branches[i], pathname);
+ }
+ return matches;
+}
+function rankRouteBranches(branches) {
+ branches.sort((a, b) => {
+ //如果分数不一样,按分数倒序排列
+ //如果分数一样,只能比过索引
+ return a.score !== b.score ? b.score - a.score : compareIndexes(
+ a.routesMeta.map(meta => meta.childrenIndex),
+ b.routesMeta.map(meta => meta.childrenIndex)
+ );
+ });
+}
+/**
+ * /user/add routesMeta=[userMeta,addMeta]=>[2,0]
+ * /user/list routesMeta = [userMeta,listMeta]=>[2,1];
+ */
+function compareIndexes(a, b) {
+ //如果级别数量相等,并且父亲都 一样,说是他们是兄弟
+ let sibling = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i])
+ //如果是兄弟的话,那和比索引,索引越小级别越高,索引越大,级别越低
+ //如果不是兄弟,那就认为相等的
+ return sibling ? a[a.length - 1] - b[b.length - 1] : 0;
+}
+/**
+ * 用分支的路径匹配地址栏的路径名
+ * @param {*} branch
+ * @param {*} pathname 完整路径
+ */
+function matchRouteBranch(branch, pathname) {
+ let { routesMeta } = branch;
+ //此分支路径参数对象 path =/:a/:b/:c pathname=/vA/vB/vC
+ let matchesParams = {};//{a:vA,b:vB,c:vC}
+ let matchedPathname = "/";
+ let matches = [];
+ for (let i = 0; i < routesMeta.length; i++) {
+ //获取当前的meta
+ let meta = routesMeta[i];
+ //判断是否是最后一个meta
+ 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(matchesParams, match.params);
+ let route = meta.route;
+ matches.push({
+ params: matchesParams,
+ pathname: joinPaths([matchedPathname, match.pathname]),
+ pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
+ route
+ });
+ if (match.pathnameBase !== '/') {
+ matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
+ }
+ }
+ return matches;
+}
+/**
+ * 匹配路径
+ * @param {*} path 路由的路径
+ * @param {*} pathname 当前地址栏中的路径
+ */
+export function matchPath({ path, end }, pathname) {
+ //把路径编译 成正则
+ let [matcher, paramNames] = compilePath(path, end);
+ //匹配结果
+ let match = pathname.match(matcher);
+ //如果没有匹配上结束
+ if (!match) {
+ return null;
+ }
+ //获取匹配的路径
+ let matchedPathname = match[0]; // /user//
+ //base就是基本路径 /user/ => /user 把结束的一个/或多个/去掉
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, '$1');
+ //拼出paramNames
+ let values = match.slice(1);
+ let captureGroups = match.slice(1);
+ let params = paramNames.reduce((memo, paramName, index) => {
+ // /user/*
+ if (paramName === '*') {
+ let splatValue = captureGroups[index] || '';//后面的内容 pathname=/user/add
+ //pathnameBase=matchedPathname=/user/add
+ //重写pathnameBase == /user/add slice=/user/ /user 截取*之前的串作为后续匹配的父串
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+/, +'$1');
+ }
+ memo[paramName] = values[index];
+ return memo;
+ }, {});
+ return {
+ params,
+ pathname: matchedPathname,//user/add
+ pathnameBase // /user
+ }
+}
function compilePath(path, end) {
//路径参数的参数名数组 /post/:id paramNames=["id"]
let paramNames = [];
let regexpSource = '^' + path
+ .replace(/\/*\*?$/, '') //的 /*或者//* 或者 * 全部转为空 /user/* /user* /user//* /user 在转正则的时候是等价的
+ .replace(/^\/*/, '/')//把开始多个/或者说没有/转成一个/ /user 不变 //user 变/user user /user
.replace(
/:(\w+)/g, (_, key) => {
paramNames.push(key);
return "([^\\/]+?)";
}
)
+ if (path.endsWith('*')) {
+ paramNames.push('*');
+ regexpSource += path === "*" || path === "/*" ? "(.*)$"
+ : "(?:\\/(.+)|\\/*)$";
+ //regexpSource += "(?:\\/(.+)|\\/*)$";
+ } else {
+ regexpSource += end ? "\\/*$" : "(?:\b|\\/|$)";
+ }
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
+const isSplat = s => s === '*';
+const splatPenalty = -2;
+const indexRouteValue = 2;
+const paramRe = /^:\w+$/;
+const dynamicSegmentValue = 3;
+const emptySegmentValue = 1;
+const staticSegmentValue = 10;
+function computeScore(path, index) {
+ let segments = path.split('/'); // /user/add => ['user','add']
+ let initialScore = segments.length;//分片的长度就是基础分数
+ if (segments.some(isSplat)) {// /user/* 有星说是通配,分数分降低
+ initialScore += splatPenalty;
+ }
+ if (index) {
+ initialScore += indexRouteValue;
+ }
+ return segments.filter(s => !isSplat(s)).reduce((score, segment) => {
+ return score + (paramRe.test(segment) ? dynamicSegmentValue : segment === '' ? emptySegmentValue : staticSegmentValue);
+ }, initialScore);
}
+/**
+ * 打平所有的分支
+ * @param {*} routes 路由配置
+ */
+function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
+ routes.forEach((route, index) => {
+ //定义一个路由元数据
+ let meta = {
+ relativePath: route.path || "",//路径相对父路径的路径 UserAdd relativePath=add
+ route, //路由对象
+ childrenIndex: index,
+ }
+ //现在我们的routes其实只有一个元素,/user/* parentPath='' relativePath=/user/*
+ //path=/user/*
+ //把父路径加上自己的相对路径构建成匹配的完整路径
+ let path = joinPaths([parentPath, meta.relativePath]);
+ //在父meta数组中添加自己这个meta
+ let routesMeta = parentsMeta.concat(meta);
+ //如果有子路由的话,递归添加到 branches分支数组中
+ if (route.children && route.children.length > 0) {
+ flattenRoutes(route.children, branches, routesMeta, path);
+ }
+ branches.push({
+ path,
+ routesMeta,
+ score: computeScore(path, route.index)
+ });
+ });
+ return branches;
+}
+function joinPaths(paths) {
+ // ['/user/*/','/add']=> /user/*/add
+ return paths.join('/').replace(/\/\/+/g, '/');
+}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, (element) => {
let route = {
path: element.props.path,// /user 此路由对应的路径
element: element.props.element // <User/> 此路由对应的元素
}
+ if (element.props.children) {
+ route.children = createRoutesFromChildren(element.props.children);
+ }
routes.push(route);
});
return routes;
}
export function useNavigate() {
const { navigator } = React.useContext(NavigationContext);
const navigate = React.useCallback((to) => navigator.push(to), [navigator]);
return navigate;
}
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 {
+ color: red;
+ }
+ </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: 'green' };
+const activeClassName = 'active';
+const activeNavProps = {
+ style: ({ isActive }) => isActive ? activeStyle : {},
+ className: ({ isActive }) => isActive ? activeClassName : ''
+}
ReactDOM.render(
<BrowserRouter>
<ul>
+ <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
+ <li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
+ <li><NavLink to="/profile" {...activeNavProps}>个人中心</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}
/>
);
}
+/**
+ *
+ * @param {*} className 类名 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
+ * @param {*} end 是否结束
+ * @param {*} style 行内样式 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
+ * @param {*} to 点击导航跳转的路径
+ * @param {*} children 子组件
+ */
+export function NavLink({ className: classNameProp = '', end = false, style: styleProp = {}, to, children, ...+rest }) {
+ let location = useLocation();
+ let path = { pathname: to };
+ let locationPathname = location.pathname;//当前的路径
+ let toPathname = path.pathname;//当前导航想要跳转的路径
+ //如果路径一样,或者 不结束,并且当前的路径是以to开头的,并且下一个字符/,也就是路径路径分隔符
+ let isActive = locationPathname === toPathname
+ || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === '/')
+ let className;
+ if (typeof classNameProp === 'function') {
+ className = classNameProp({
+ isActive
+ });
+ }
+ let style;
+ if (typeof styleProp === 'function') {
+ style = styleProp({
+ isActive
+ });
+ }
+ return (
+ <Link {...rest} to={to} className={className} style={style}>{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: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
ReactDOM.render(
<BrowserRouter>
<ul>
<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</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, { memo } from 'react';
//导航上下文
const NavigationContext = React.createContext();
//路径上下文
const LocationContext = React.createContext();
//路由上下文
const RouteContext = React.createContext();
export { NavigationContext, LocationContext, RouteContext }
export function Outlet() {
return useOutlet();
}
function useOutlet() {
let { outlet } = React.useContext(RouteContext);
return outlet;
}
export function useParams() {
let { matches } = React.useContext(RouteContext);
let routeMatch = matches[matches.length - 1];
return routeMatch ? routeMatch.params : {};
}
/**
*
* @param {*} children 子组件
* @param {*} location 当前的路径对象
* @param {*} navigator history对象 go back forward push....
*/
export function Router({ children, location, navigator }) {
const navigationContext = React.useMemo(() => ({ navigator }), [navigator]);
const locationContext = React.useMemo(() => ({ location }), [location]);
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider value={locationContext} children={children} />
</NavigationContext.Provider>
)
}
export function Routes({ children }) {
const routes = createRoutesFromChildren(children);
return useRoutes(routes)
}
//
export function Route() { }
export function useLocation() {
return React.useContext(LocationContext).location;
}
/**
* 把此路由配置数组渲染成真正的组件
* @param {*} routes 路由配置数组
*/
export function useRoutes(routes) {
//当前的路径对象
let location = useLocation();
//当前的路径字符串 /user/add
let pathname = location.pathname;
//用当前的地址栏中的路径和路由进行匹配
let matches = matchRoutes(routes, { pathname });
//渲染匹配的结果
return _renderMatches(matches);
}
function _renderMatches(matches) {
if (!matches) return null;
//渲染结果的时候是从右向左执行的
//matches=[{route:{element:User}},{route:{element:UserAdd]}}]
return matches.reduceRight((outlet, match, index) => {
return (
<RouteContext.Provider value={{ outlet, matches: matches.slice(0, index + 1) }}>
{match.route.element}
</RouteContext.Provider>
)
}, null);
}
/**
* 用当前路径和路由配置进行匹配,获取匹配的结果
* @param {*} routes 路由配置
* @param {*} location 当前路径
*/
function matchRoutes(routes, location) {
//获取路径名
let pathname = location.pathname;
//打平所有的分支路径
let branches = flattenRoutes(routes);
rankRouteBranches(branches);
console.log(branches);
//匹配的结果
let matches = null;
//按分支顺序依次进行匹配,如果匹配上了,直接退出循环,不再进行后续的匹配
for (let i = 0; matches == null && i < branches.length; i++) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
function rankRouteBranches(branches) {
branches.sort((a, b) => {
//如果分数不一样,按分数倒序排列
//如果分数一样,只能比过索引
return a.score !== b.score ? b.score - a.score : compareIndexes(
a.routesMeta.map(meta => meta.childrenIndex),
b.routesMeta.map(meta => meta.childrenIndex)
);
});
}
/**
* /user/add routesMeta=[userMeta,addMeta]=>[2,0]
* /user/list routesMeta = [userMeta,listMeta]=>[2,1];
*/
function compareIndexes(a, b) {
//如果级别数量相等,并且父亲都 一样,说是他们是兄弟
let sibling = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i])
//如果是兄弟的话,那和比索引,索引越小级别越高,索引越大,级别越低
//如果不是兄弟,那就认为相等的
return sibling ? a[a.length - 1] - b[b.length - 1] : 0;
}
/**
* 用分支的路径匹配地址栏的路径名
* @param {*} branch
* @param {*} pathname 完整路径
*/
function matchRouteBranch(branch, pathname) {
let { routesMeta } = branch;
//此分支路径参数对象 path =/:a/:b/:c pathname=/vA/vB/vC
let matchesParams = {};//{a:vA,b:vB,c:vC}
let matchedPathname = "/";
let matches = [];
for (let i = 0; i < routesMeta.length; i++) {
//获取当前的meta
let meta = routesMeta[i];
//判断是否是最后一个meta
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(matchesParams, match.params);
let route = meta.route;
matches.push({
params: matchesParams,
pathname: joinPaths([matchedPathname, match.pathname]),
pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
route
});
if (match.pathnameBase !== '/') {
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
}
}
return matches;
}
/**
* 匹配路径
* @param {*} path 路由的路径
* @param {*} pathname 当前地址栏中的路径
*/
export function matchPath({ path, end }, pathname) {
//把路径编译 成正则
let [matcher, paramNames] = compilePath(path, end);
//匹配结果
let match = pathname.match(matcher);
//如果没有匹配上结束
if (!match) {
return null;
}
//获取匹配的路径
let matchedPathname = match[0]; // /user//
//base就是基本路径 /user/ => /user 把结束的一个/或多个/去掉
let pathnameBase = matchedPathname.replace(/(.)\/+$/, '$1');
//拼出paramNames
let values = match.slice(1);
let captureGroups = match.slice(1);
let params = paramNames.reduce((memo, paramName, index) => {
// /user/*
if (paramName === '*') {
let splatValue = captureGroups[index] || '';//后面的内容 pathname=/user/add
//pathnameBase=matchedPathname=/user/add
//重写pathnameBase == /user/add slice=/user/ /user 截取*之前的串作为后续匹配的父串
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+/, '$1');
}
memo[paramName] = values[index];
return memo;
}, {});
return {
params,
pathname: matchedPathname,//user/add
pathnameBase // /user
}
}
function compilePath(path, end) {
//路径参数的参数名数组 /post/:id paramNames=["id"]
let paramNames = [];
let regexpSource = '^' + path
.replace(/\/*\*?$/, '') //的 /*或者//* 或者 * 全部转为空 /user/* /user* /user//* /user 在转正则的时候是等价的
.replace(/^\/*/, '/')//把开始多个/或者说没有/转成一个/ /user 不变 //user 变/user user /user
.replace(
/:(\w+)/g, (_, key) => {
paramNames.push(key);
return "([^\\/]+?)";
}
)
if (path.endsWith('*')) {
paramNames.push('*');
// Already matched the initial /, just match the rest
regexpSource += path === "*" || path === "/*" ? "(.*)$"
: "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
//regexpSource += "(?:\\/(.+)|\\/*)$";
} else {
regexpSource += end ? "\\/*$" : "(?:\b|\\/|$)";
}
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
const isSplat = s => s === '*';
const splatPenalty = -2;
const indexRouteValue = 2;
const paramRe = /^:\w+$/;
const dynamicSegmentValue = 3;
const emptySegmentValue = 1;
const staticSegmentValue = 10;
function computeScore(path, index) {
let segments = path.split('/'); // /user/add => ['user','add']
let initialScore = segments.length;//分片的长度就是基础分数
if (segments.some(isSplat)) {// /user/* 有星说是通配,分数分降低
initialScore += splatPenalty;
}
if (index) {
initialScore += indexRouteValue;
}
//1.过滤*
//
return segments.filter(s => !isSplat(s)).reduce((score, segment) => {
return score + (paramRe.test(segment) ? dynamicSegmentValue : segment === '' ? emptySegmentValue : staticSegmentValue);
}, initialScore);
}
/**
* 打平所有的分支
* @param {*} routes 路由配置
*/
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
routes.forEach((route, index) => {
//定义一个路由元数据
let meta = {
relativePath: route.path || "",//路径相对父路径的路径 UserAdd relativePath=add
route, //路由对象
childrenIndex: index,
}
//现在我们的routes其实只有一个元素,/user/* parentPath='' relativePath=/user/*
//path=/user/*
//把父路径加上自己的相对路径构建成匹配的完整路径
let path = joinPaths([parentPath, meta.relativePath]);
//在父meta数组中添加自己这个meta
let routesMeta = parentsMeta.concat(meta);
//如果有子路由的话,递归添加到 branches分支数组中
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
branches.push({
path,
routesMeta,
score: computeScore(path, route.index)
});
});
return branches;
}
function joinPaths(paths) {
// ['/user/*/','/add']=> /user/*/add
return paths.join('/').replace(/\/\/+/g, '/');
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, (element) => {
let route = {
path: element.props.path,// /user 此路由对应的路径
element: element.props.element // <User/> 此路由对应的元素
}
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
export function useNavigate() {
// navigator history
// Navigate动词表示导航 或者叫跳转
const { navigator } = React.useContext(NavigationContext);
const navigate = React.useCallback((to) => navigator.push(to), [navigator]);
return navigate;
}
+export function Navigate({ to }) {
+ let navigate = useNavigate();
+ React.useLayoutEffect(() => {
+ 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: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
ReactDOM.render(
<BrowserRouter>
<ul>
<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</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} path="/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';
const activeStyle = { backgroundColor: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
+import routesConfig from './routesConfig';
+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 end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
<li><NavLink to="/foo" {...activeNavProps}>foo</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;