<

内容大纲 #

1. React路由原理 #

1.1 HashRouter #

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>

1.2 BrowserRouter #

1.2.1 history #

1.2.1.1 pushState #
1.2.1.2 replaceState #
1.2.1.3 onpopstate #
1.2.1.4 案例 #
<!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>

2.使用基本路由 #

2.1 安装 #

npm i react-router-dom --save

2.2 src\index.js #

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'));

2.3 Home.js #

src\components\Home.js

import React from 'react';
function Home(props) {
    console.log(props);
    return (
        <div>Home</div>
    )
}
export default Home;

2.4 User.js #

src\components\User.js

import React from 'react';
function User() {
    return (
        <div>User</div>
    )
}
export default User;

2.5 Profile.js #

src\components\Profile.js

import React from 'react';
function Profile() {
    return (
        <div>Profile</div>
    )
}
export default Profile;

3.实现基本路由 #

3.1 react-router-dom\index.js #

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}
        />
    );
}

3.2 src\react-router\index.js #

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;
}

4.实现history #

4.1 createBrowserHistory.js #

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;

4.2 createHashHistory.js #

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;

4.3 history\index.js #

src\history\index.js

export {default as createBrowserHistory} from './createBrowserHistory';
export {default as createHashHistory} from './createHashHistory';

4.4 react-router-dom\index.js #

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}
        />
    );
}

5. path-to-regexp #

5.1 /home结束 #

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'));

homereg

5.2 /home非结束 #

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'));

homereg2

5.3 路径参数 #

let params = [];
let regExp = pathToRegExp('/user/:id',params,{end:true});
console.log(regExp,params);
/**
/^\/user\/(?:([^\/]+?))\/?$/i
[ { name: 'id', optional: false, offset: 7 } ]
**/

uerreg

5.4 正则匹配 #

表达式 含义
() 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容)
(?:) 表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来
(?...) 表示命名捕获分组,反向引用一个命名分组的语法是 \k,在 replace() 方法的替换字符串中反向引用是用 $
//分组获取
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);

6. 路径参数 #

6.1 src\index.js #

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'));

6.2 src\react-router\index.js #

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 };
}

6.3 Post.js #

src\components\Post.js

import React from 'react';
function Post(props) {
    return (
        <div>Post</div>
    )
}
export default Post;

7. Link导航 #

7.1 src\index.js #

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'));

7.2 react-router-dom\index.js #

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>
+  )
+}

7.3 react-router\index.js #

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;
+}

8. 支持嵌套路由和Outlet #

8.1 数据结构 #

8.1.1 routes #

[
  {
    "path": "/user/*",
    "element": "element",
    "children": [
      {
        "path": "add",
        "element": "element"
      }
    ]
  }
]

8.1.2 branches #

{
{
  "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]},
]

8.1.3 flattenRoutes #

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));

8.1.4 remainingPathname #

let pathname = '/user/add';
let meta = '/user';
let remainingPathname = pathname.slice(meta.length);///add

8.1.5 replace #

let str = '/user///add';
str = str.replace(/\/\/+/g, '/');
console.log(str);

8.1.6 regexpSource #

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

8.1.7 pathnameBase #

8.1.8 computeScore #

//如果路径中有*就减少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));

8.1.9 compareIndexes #

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]));

8.2 src\index.js #

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'));

8.3 User.js #

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;

8.4 src\utils.js #

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);
    }
}

8.5 UserAdd.js #

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>
  )
}

8.6 UserDetail.js #

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>
  )
}

8.7 UserList.js #

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>
  )
}

8.8 react-router\index.js #

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;
}

9.1 public\index.html #

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>

9.2 src\index.js #

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'));

9.3 src\react-router-dom\index.js #

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>
+  )
+}

10. 跳转和重定向 #

10.1 src\index.js #

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'));

10.2 Home.js #

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;

10.3 react-router\index.js #

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;
+}

11. 受保护路由 #

11.1 src\index.js #

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'));

11.2 Login.js #

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;

11.3 Protected.js #

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;

12. 配置式路由和懒加载 #

12.1 src\index.js #

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'));

12.2 src\routesConfig.js #

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;

12.3 NotFound.js #

src\components\NotFound.js

import React from 'react';
function NotFound(props) {
    return (
        <div>NotFound</div>
    )
}
export default NotFound;