内容大纲 #

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 name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #root{
            border:1px solid red;
            height:20px;
        }
    </style>
</head>
<body>
    <div id="root"></div>
    <script>
        var historyObj = window.history;
        //监听路径改变事件 表示将当前的状态变更了,弹出了
        window.onpushstate = (event) => {
            console.log(event.type,event.detail.state);
            root.innerHTML = window.location.pathname;
        }
        window.addEventListener('popstate', (event) => {
            console.log(event.type,event.state);
            root.innerHTML = window.location.pathname;
        });

        (function (historyObj) {
            let oldPushState = history.pushState;//缓存原生的pushState
            historyObj.pushState = function (state, title, pathname) {
                let result = oldPushState.apply(history, arguments);
                if (typeof window.onpushstate === 'function') {
                    window.onpushstate(new CustomEvent('pushstate',{detail:{pathname,state}}));
                }
                return result;
            }
        })(historyObj);
        let oldHistoryLength = historyObj.length;
        setTimeout(() => {
            historyObj.pushState({ page: 1 }, { title: 'page1' }, '/page1');//page1
            console.log(historyObj.length-oldHistoryLength);
        }, 1000);
        setTimeout(() => {
            historyObj.pushState({ page: 2 }, { title: 'page2' }, '/page2');//page2
            console.log(historyObj.length-oldHistoryLength);
        }, 2000);
        setTimeout(() => {
            historyObj.pushState({ page: 3 }, { title: 'page3' }, '/page3');//page3
            console.log(historyObj.length-oldHistoryLength);
        }, 3000);
        setTimeout(() => {
            historyObj.back();//historyObj.go(-1);//page2
            setTimeout(()=>console.log(historyObj.length-oldHistoryLength),100);

        }, 4000);
        setTimeout(() => {
            historyObj.pushState({ page:4 }, { title: 'page4' }, '/page4');//page4
            console.log(historyObj.length-oldHistoryLength);
        }, 5000);
        setTimeout(() => {
            historyObj.go(1);
            console.log(historyObj.length-oldHistoryLength);//page4
        }, 6000);
    </script>
</body>
</html>

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({});
//路由上下文
const RouteContext = React.createContext({});

export {
    NavigationContext,
    LocationContext,
    RouteContext
};
export function Router({ children, location, navigator }) {
    let navigationContext = React.useMemo(
        () => ({ navigator }),
        [navigator]
    );
    return (
        <NavigationContext.Provider value={navigationContext}>
            <LocationContext.Provider
                children={children}
                value={{ location }}
            />
        </NavigationContext.Provider>
    );
}
export function Routes({ children }) {
    return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
    return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
    let location = useLocation();//当前的路径对象
    let pathname = location.pathname || "/";//当前的路径
    for (let i = 0; i < routes.length; i++) {
        let { path, element } = routes[i];
        let match = matchPath(path, pathname);
        if (match) {
            return element;
        }
    }
    return null;
}

export function createRoutesFromChildren(children) {
    let routes = [];
    React.Children.forEach(children, element => {
        let route = {
            path: element.props.path,
            element: element.props.element
        };
        routes.push(route);
    });
    return routes;
}

export function Route(props) {

}
function compilePath(path) {
    let regexpSource = "^" + path;
    regexpSource += "$";
    let matcher = new RegExp(regexpSource);
    return matcher;
}
export function matchPath(path, pathname) {
    let matcher = compilePath(path);
    let match = pathname.match(matcher);
    if (!match) return null;
    return match;
}

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:'/',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(/(?<x>\d{2})-(?<y>\d{2})/.exec('11-22'));
console.log('11-22'.replace(/(?<x>\d{2})-(?<y>\d{2})/,"$<y>-$<x>"));
表达式 含义
(?=pattern) 正向肯定查找(前瞻),后面必须跟着什么
(?!pattern) 正向否定查找(前瞻),后面不能跟着什么
(?<=pattern) 反向肯定条件查找(后顾),不捕获
(?<!pattern) 反向否定条件查找(后顾)
//会消耗掉字符的
//console.log('1a'.match(/\d[a-z][a-z]/));
//?= 正向肯定查找 不消费字符 正向前瞻
//console.log('1a'.match(/\d(?=[a-z])[a-z]/));

//正向肯定前瞻
console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向否定前瞻
console.log('1a'.match(/\d(?![A-Z])[a-z]/));
//反向肯定前瞻
console.log('1a'.match(/(?<=[a-z])\d[a-z]/));
//反向否定前瞻
console.log('1a'.match(/(?<![A-Z])\d[a-z]/));

let array = ['1ab'];
array.index = 0;
array.input = '1ab';
array.groups = undefined;
console.log(array);

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({});
//路由上下文
const RouteContext = React.createContext({});

export {
    NavigationContext,
    LocationContext,
    RouteContext
};
export function Router({ children, location, navigator }) {
    let navigationContext = React.useMemo(
        () => ({ navigator }),
        [navigator]
    );
    return (
        <NavigationContext.Provider value={navigationContext}>
            <LocationContext.Provider
                children={children}
                value={{ location }}
            />
        </NavigationContext.Provider>
    );
}
export function Routes({ children }) {
    return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
    return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
    let location = useLocation();//当前的路径对象
    let pathname = location.pathname || "/";//当前的路径
    for (let i = 0; i < routes.length; i++) {
        let { path, element } = routes[i];
        let match = matchPath(path, pathname);
        if (match) {
+           return React.cloneElement(element, { ...element.props, match });
        }
    }
    return null;
}

export function createRoutesFromChildren(children) {
    let routes = [];
    React.Children.forEach(children, element => {
        let route = {
            path: element.props.path,
            element: element.props.element
        };
        routes.push(route);
    });
    return routes;
}

export function Route(props) {

}
function compilePath(path) {
+   let paramNames = [];
+   let regexpSource = "^" + path
+       .replace(/:(\w+)/g, (_, key) => {
+           paramNames.push(key);
+           return "([^\\/]+)";
+       });
+    regexpSource += "$";
    let matcher = new RegExp(regexpSource);
+   return [matcher, paramNames];
}
export function matchPath(path, pathname) {
+   let [matcher, paramNames] = compilePath(path);
    let match = pathname.match(matcher);
    if (!match) return null;
+   let matchedPathname = match[0];
+   let values = match.slice(1);
+   let params = paramNames.reduce(
+       (memo, paramName, index) => {
+           memo[paramName] = values[index];
+           return memo;
+       },
+       {}
+   );
+   return { params, pathname: matchedPathname, path };
}

6.3 Post.js #

src\components\Post.js

import React from 'react';
function Post(props) {
    console.log(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, ...rest }) {
+    let navigate = useNavigate();
+    function handleClick() {
+        navigate(to);
+    }
+    return (
+        <a
+            {...rest}
+            href={to}
+            onClick={handleClick}
+        />
+    );
+}

7.3 react-router\index.js #

src\react-router\index.js

import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({});

export {
    NavigationContext,
    LocationContext,
    RouteContext
};
export function Router({ children, location, navigator }) {
    let navigationContext = React.useMemo(
        () => ({ navigator }),
        [navigator]
    );
    return (
        <NavigationContext.Provider value={navigationContext}>
            <LocationContext.Provider
                children={children}
                value={{ location }}
            />
        </NavigationContext.Provider>
    );
}
export function Routes({ children }) {
    return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
    return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
    let location = useLocation();//当前的路径对象
    let pathname = location.pathname || "/";//当前的路径
    for (let i = 0; i < routes.length; i++) {
        let { path, element } = routes[i];
        let match = matchPath(path, pathname);
        if (match) {
            return React.cloneElement(element, { ...element.props, match });
        }
    }
    return null;
}

export function createRoutesFromChildren(children) {
    let routes = [];
    React.Children.forEach(children, element => {
        let route = {
            path: element.props.path,
            element: element.props.element
        };
        routes.push(route);
    });
    return routes;
}

export function Route(props) {

}

function compilePath(path) {
    let paramNames = [];
    let regexpSource = "^" + path
        .replace(/:(\w+)/g, (_, key) => {
            paramNames.push(key);
            return "([^\\/]+)";
        });
    regexpSource += "$";
    let matcher = new RegExp(regexpSource);
    return [matcher, paramNames];
}
export function matchPath(path, pathname) {
    let [matcher, paramNames] = compilePath(path);
    let match = pathname.match(matcher);
    if (!match) return null;
    let matchedPathname = match[0];
    let values = match.slice(1);
    let params = paramNames.reduce(
        (memo, paramName, index) => {
            memo[paramName] = values[index];
            return memo;
        },
        {}
    );
    return { params, pathname: matchedPathname, path };
}

+export function useNavigate() {
+    let { navigator } = React.useContext(NavigationContext);
+    let navigate = React.useCallback((to) => {
+        navigator.push(to);
+    }, [navigator]);
+    return navigate;
+}

8. 支持嵌套路由和Outlet #

8.1 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.2 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.3 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.4 UserAdd.js #

src\components\UserAdd.js

import React from 'react';
import { useNavigate } from '../react-router-dom';
import { UserAPI } from '../utils';
function UserAdd(props) {
    const usernameRef = React.useRef();
    const navigate = useNavigate();
    const handleSubmit = (event) => {
        event.preventDefault();
        let username = usernameRef.current.value;
        UserAPI.add({ id: Date.now() + '', username });
        navigate('/user/list');
    }
    return (
        <form onSubmit={handleSubmit}>
            <input type="text" ref={usernameRef} />
            <button type="submit" >提交</button>
        </form>
    )
}

export default UserAdd;

8.5 UserDetail.js #

src\components\UserDetail.js

import React from 'react';
import { UserAPI } from '../utils';
function UserList() {
    const [user, setUser] = React.useState({});
    React.useEffect(() => {
        let user = this.props.location.state;
        if (!user) {
            let id = this.props.match.params.id;
            user = UserAPI.find(id);
        }
        if (user) setUser({ user });
    }, []);
    return (
        <div>
            {user.id}:{user.username}
        </div>
    )
}
export default UserList;

8.6 UserList.js #

src\components\UserList.js

import React from 'react'
import { Link } from '../react-router-dom';
import { UserAPI } from '../utils';
function UserList() {
    const [users, setUsers] = React.useState([]);
    React.useEffect(() => {
        let users = UserAPI.list();
        setUsers(users);
    }, []);
    return (
        <ul >
            {
                users.map((user, index) => (
                    <li key={index}>
                        <Link to={{ pathname: `/user/detail/${user.id}`, state: user }}>{user.username}</Link>
                    </li>
                ))
            }
        </ul>
    )
}
export default UserList;

8.7 react-router\index.js #

src\react-router\index.js

import React from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
+//路由上下文
+const RouteContext = React.createContext({
+    outlet: null,
+    matches: []
+});
+const OutletContext = React.createContext();
+const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
+export function Outlet(props) {
+    return useOutlet(props.context);
+}
+export function useOutlet(context) {
+    let outlet = React.useContext(RouteContext).outlet;
+    if (outlet) {
+        return <OutletContext.Provider value={context} children={outlet} />;
+    }
+    return outlet;
+}
export function Router({ children, location, navigator }) {
    let navigationContext = React.useMemo(
        () => ({ navigator }),
        [navigator]
    );
    return (
        <NavigationContext.Provider value={navigationContext}>
            <LocationContext.Provider
                children={children}
                value={{ location }}
            />
        </NavigationContext.Provider>
    );
}
export function Routes({ children }) {
    return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
    return React.useContext(LocationContext).location;
}
+export function useParams() {
+  let {matches} = React.useContext(RouteContext);
+  let routeMatch = matches[matches.length - 1];
+  return routeMatch ? routeMatch.params : {};
+}

export function useRoutes(routes) {
+   //当前的路径对象
+   let location = useLocation();
+   //当前的路径字符串
+   let pathname = location.pathname || "/";
+   //匹配路径
+   let matches = matchRoutes(routes, { pathname });
+   //渲染匹配的结果
+   return _renderMatches(matches);
}
+function _renderMatches(matches, parentMatches = []) {
+    if (matches == null) return null;
+    return matches.reduceRight((outlet, match, index) => {
+        return (
+            <RouteContext.Provider children={match.route.element || <Outlet />} value={{
+                outlet,
+                matches: parentMatches.concat(matches.slice(0, index + 1))
+            }} />
+        );
+    }, null);
+}
+export function matchRoutes(routes, location) {
+    //获取路径
+    let pathname = location.pathname || "/";
+    //打平所有的分支
+    let branches = flattenRoutes(routes);
+    rankRouteBranches(branches);
+    //匹配路由分支
+    let matches = null;
+    for (let i = 0; matches == null && i < branches.length; ++i) {
+        matches = matchRouteBranch(branches[i], pathname);
+    }
+    return matches;
+}
+const paramRe = /^:\w+$/;
+const dynamicSegmentValue = 3;
+const indexRouteValue = 2;
+const emptySegmentValue = 1;
+const staticSegmentValue = 10;
+const splatPenalty = -2;
+
+const isSplat = s => s === "*";
+
+function computeScore(path, index) {
+  let segments = path.split("/");
+  let initialScore = segments.length;
+
+  if (segments.some(isSplat)) {
+    initialScore += splatPenalty;
+  }
+
+  if (index) {
+    initialScore += indexRouteValue;
+  }
+  return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
+}
+function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
+    routes.forEach((route) => {
+        let meta = {
+            relativePath: route.path || "",//路由的相对路径
+            route //路由对象
+        };
+        //父路径和子路径拼在一起成为当前的路径
+        let path = joinPaths([parentPath, meta.relativePath]);
+        //各父Meta数组中添加当前的meta
+        let routesMeta = parentsMeta.concat(meta);
+        if (route.children && route.children.length > 0) {
+            flattenRoutes(route.children, branches, routesMeta, path);
+        }
+        branches.push({
+            path,//此配置的完整路径
+            score: computeScore(path, route.index),  rankRouteBranches(branches);
+            routesMeta //此配置的meta数据
+        });
+    });
+    return branches;
+}
+function rankRouteBranches(branches) {
+  branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
+}
+function compareIndexes(a, b) {
+    let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
+    return siblings ? a[a.length - 1] - b[b.length - 1] : 0;
+}
+/**
+ * 匹配路由的分支
+ * @param {*} branch  /user/星/add
+ * @param {*} pathname 
+ * @returns 
+ */
+function matchRouteBranch(branch, pathname) {
+    let { routesMeta } = branch;
+    let matchedParams = {};
+    let matchedPathname = "/";
+    let matches = [];
+    for (let i = 0; i < routesMeta.length; ++i) {
+        //获取当前的meta
+        let meta = routesMeta[i];
+        //是否是最后一个
+        let end = i === routesMeta.length - 1;
+        //获取剩下的路径
+        let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
+        //判断路径是否匹配
+        let match = matchPath({
+            path: meta.relativePath,
+            end
+        }, remainingPathname);
+        if (!match) return null;
+        Object.assign(matchedParams, match.params);
+        let route = meta.route;
+        matches.push({
+            params: matchedParams,
+            pathname: joinPaths([matchedPathname, match.pathname]),
+            pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
+            route
+        });
+        if (match.pathnameBase !== "/") {
+            matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
+        }
+    }
+    return matches;
+}


export function createRoutesFromChildren(children) {
    let routes = [];
    React.Children.forEach(children, element => {
        let route = {
            path: element.props.path,
            element: element.props.element
        };
+       if (element.props.children) {
+           route.children = createRoutesFromChildren(element.props.children);
+       }
        routes.push(route);
    });
    return routes;
}

export function Route(props) {

}
function compilePath(path, end) {
    let paramNames = [];
    let regexpSource = "^" + path
+       .replace(/\/*\*?$/, "")
+       .replace(/^\/*/, "/")
        .replace(/:(\w+)/g, (_, key) => {
            paramNames.push(key);
            return "([^\\/]+)";
        });
+   if (path.endsWith("*")) {
+       paramNames.push("*");
+       regexpSource += "(?:\\/(.+)|\\/*)$";
+   } else {
+       regexpSource += end ? "\\/*$" : "(?:\\b|\\/|$)";
+   }
    let matcher = new RegExp(regexpSource);
    return [matcher, paramNames];
}
+export function matchPath(pattern, pathname) {
+    let [matcher, paramNames] = compilePath(pattern.path, pattern.end);
+    //获取匹配结果
+    let match = pathname.match(matcher);
+    if (!match) return null;
+    //获取匹配的路径
+    let matchedPathname = match[0];
+    //只要当前的路径
+    let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
+    //获取后面的分组的值
+    let values = match.slice(1);
+    //获取捕获的分组
+    let captureGroups = match.slice(1);
+    //遍历参数名称
+    let params = paramNames.reduce(
+        (memo, paramName, index) => {
+            if (paramName === "*") {
+                let splatValue = captureGroups[index] || "";
+                pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
+            }
+            memo[paramName] = values[index];
+            return memo;
+        },
+        {}
+    );
+    return {
+        params,
+        pathname: matchedPathname,
+        pathnameBase,
+        pattern
+    };
+}

export function useNavigate() {
    let { navigator } = React.useContext(NavigationContext);
    let navigate = React.useCallback((to) => {
        navigator.push(to);
    }, [navigator]);
    return navigate;
}

pathnameBase

let pathname = '/user/*';
let matcher = /^\/user(?:\/(.+)|\/*)$/i;
let match = pathname.match(matcher);
let matchedPathname = match[0];
let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
let captureGroups = match.slice(1);
let paramNames = ["*"];
let params = paramNames.reduce((memo, paramName, index) => {
    if (paramName === "*") {
        let splatValue = captureGroups[index];//add
        pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
    }
    memo[paramName] = captureGroups[index];
    return memo;
}, {});
console.log(params);
console.log(pathnameBase);

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 {
+     text-decoration: line-through;
+   }
+ </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: "red" };
+const activeClassName = 'active';
ReactDOM.render(
    <BrowserRouter>
        <ul>
+           <li>
+               <NavLink to="/"
+                   style={({ isActive }) => isActive ? activeStyle : {}}
+                   className={({ isActive }) => isActive ? activeClassName : ''}
+               >首页</NavLink>
+           </li>
+           <li><NavLink to="/user" >用户管理</NavLink></li>
+           <li><NavLink to="/profile" >个人中心</NavLink></li>
        </ul>
        <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/user/*" element={<User />} >
                <Route path="add" element={<UserAdd />} />
                <Route path="list" element={<UserList />} />
                <Route path="detail/:id" element={<UserDetail />} />
            </Route>
            <Route path="/profile" element={<Profile />} />
            <Route path="/post/:id" element={<Post />} />
        </Routes>
    </BrowserRouter>
    , document.getElementById('root'));

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}
        />
    );
}
+export const NavLink = function ({
+    className: classNameProp = "",
+    end = false,
+    style: styleProp = {},
+    to,
+    children,
+    ...rest
+}) {
+    let location = useLocation();
+    let path = { pathname: to };
+    let locationPathname = location.pathname;
+    let toPathname = path.pathname;
+    let isActive = locationPathname === toPathname || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === "/");
+    let className;
+    if (typeof classNameProp === "function") {
+        debugger
+        className = classNameProp({
+            isActive
+        });
+    } else {
+        className = [classNameProp, isActive ? "active" : null].filter(Boolean).join(" ");
+    }
+    let style = typeof styleProp === "function" ? styleProp({
+        isActive
+    }) : styleProp;
+    return (
+        <Link {...rest} className={className} style={style} to={to}>
+            {children}
+        </Link>
+    );
+}

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: "red" };
const activeClassName = 'active';
ReactDOM.render(
    <BrowserRouter>
        <ul>
            <li>
                <NavLink to="/"
                    style={({ isActive }) => isActive ? activeStyle : {}}
                    className={({ isActive }) => isActive ? activeClassName : ''}
                >首页</NavLink>
            </li>
            <li><NavLink to="/user" >用户管理</NavLink></li>
            <li><NavLink to="/profile" >个人中心</NavLink></li>
        </ul>
        <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/user/*" element={<User />} >
                <Route path="add" element={<UserAdd />} />
                <Route path="list" element={<UserList />} />
                <Route path="detail/:id" element={<UserDetail />} />
            </Route>
            <Route path="/profile" element={<Profile />} />
            <Route path="/post/:id" element={<Post />} />
+           <Route path="*" element={<Navigate to="/" />} />
        </Routes>
    </BrowserRouter>
    , document.getElementById('root'));

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 from 'react';
//导航上下文
const NavigationContext = React.createContext({});
//路径上下文
const LocationContext = React.createContext({});
//路由上下文
const RouteContext = React.createContext({
    outlet: null,
    matches: []
});
const OutletContext = React.createContext();
const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
export function Outlet(props) {
    return useOutlet(props.context);
}
export function useOutlet(context) {
    let outlet = React.useContext(RouteContext).outlet;
    if (outlet) {
        return <OutletContext.Provider value={context} children={outlet} />;
    }
    return outlet;
}
export function Router({ children, location, navigator }) {
    let navigationContext = React.useMemo(
        () => ({ navigator }),
        [navigator]
    );
    return (
        <NavigationContext.Provider value={navigationContext}>
            <LocationContext.Provider
                children={children}
                value={{ location }}
            />
        </NavigationContext.Provider>
    );
}
export function Routes({ children }) {
    return useRoutes(createRoutesFromChildren(children));
}
export function useLocation() {
    return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
    //当前的路径对象
    let location = useLocation();
    //当前的路径字符串
    let pathname = location.pathname || "/";
    //匹配路径
    let matches = matchRoutes(routes, { pathname });
    //渲染匹配的结果
    return _renderMatches(matches);
}
function _renderMatches(matches, parentMatches = []) {
    if (matches == null) return null;
    return matches.reduceRight((outlet, match, index) => {
        return (
            <RouteContext.Provider children={match.route.element || <Outlet />} value={{
                outlet,
                matches: parentMatches.concat(matches.slice(0, index + 1))
            }} />
        );
    }, null);
}
export function matchRoutes(routes, location) {
    //获取路径
    let pathname = location.pathname || "/";
    //打平所有的分支
    let branches = flattenRoutes(routes);
    //匹配路由分支
    let matches = null;
    for (let i = 0; matches == null && i < branches.length; ++i) {
        matches = matchRouteBranch(branches[i], pathname);
    }
    return matches;
}
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
    routes.forEach((route) => {
        let meta = {
            relativePath: route.path || "",//路由的相对路径
            route //路由对象
        };
        //父路径和子路径拼在一起成为当前的路径
        let path = joinPaths([parentPath, meta.relativePath]);
        //各父Meta数组中添加当前的meta
        let routesMeta = parentsMeta.concat(meta);
        if (route.children && route.children.length > 0) {
            flattenRoutes(route.children, branches, routesMeta, path);
        }
        branches.push({
            path,//此配置的完整路径
            routesMeta //此配置的meta数据
        });
    });
    return branches;
}

/**
 * 匹配路由的分支
 * @param {*} branch  /user/星/add
 * @param {*} pathname 
 * @returns 
 */
function matchRouteBranch(branch, pathname) {
    let { routesMeta } = branch;
    let matchedParams = {};
    let matchedPathname = "/";
    let matches = [];
    for (let i = 0; i < routesMeta.length; ++i) {
        //获取当前的meta
        let meta = routesMeta[i];
        //是否是最后一个
        let end = i === routesMeta.length - 1;
        //获取剩下的路径
        let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
        //判断路径是否匹配
        let match = matchPath({
            path: meta.relativePath,
            end
        }, remainingPathname);
        if (!match) return null;
        Object.assign(matchedParams, match.params);
        let route = meta.route;
        matches.push({
            params: matchedParams,
            pathname: joinPaths([matchedPathname, match.pathname]),
            pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
            route
        });
        if (match.pathnameBase !== "/") {
            matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
        }
    }
    return matches;
}


export function createRoutesFromChildren(children) {
    let routes = [];
    React.Children.forEach(children, element => {
        let route = {
            path: element.props.path,
            element: element.props.element
        };
        if (element.props.children) {
            route.children = createRoutesFromChildren(element.props.children);
        }
        routes.push(route);
    });
    return routes;
}

export function Route(props) {

}
function compilePath(path, end) {
    let paramNames = [];
    let regexpSource = "^" + path
        .replace(/^\/*/, "/")
        .replace(/:(\w+)/g, (_, key) => {
            paramNames.push(key);
            return "([^\\/]+)";
        });
    if (path.endsWith("*")) {
        paramNames.push("*");
        regexpSource += "(?:\\/(.+)|\\/*)$";
    } else {
        regexpSource += end ? "\\/*$" : "(?:\\b|\\/|$)";
    }
    let matcher = new RegExp(regexpSource);
    return [matcher, paramNames];
}
export function matchPath(pattern, pathname) {
    let [matcher, paramNames] = compilePath(pattern.path, pattern.end);
    //获取匹配结果
    let match = pathname.match(matcher);
    if (!match) return null;
    //获取匹配的路径
    let matchedPathname = match[0];
    //只要当前的路径
    let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
    //获取后面的分组的值
    let values = match.slice(1);
    //获取捕获的分组
    let captureGroups = match.slice(1);
    //遍历参数名称
    let params = paramNames.reduce(
        (memo, paramName, index) => {
            if (paramName === "*") {
                let splatValue = captureGroups[index] || "";
                pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
            }
            memo[paramName] = values[index];
            return memo;
        },
        {}
    );
    return {
        params,
        pathname: matchedPathname,
        pathnameBase,
        pattern
    };
}

export function useNavigate() {
    let { navigator } = React.useContext(NavigationContext);
    let navigate = React.useCallback((to) => {
        navigator.push(to);
    }, [navigator]);
    return navigate;
}

+export function Navigate({ to }) {
+    let navigate = useNavigate();
+    React.useEffect(() => {
+        debugger
+        navigate(to);
+    });
+    return null;
+}

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: "red" };
const activeClassName = 'active';
ReactDOM.render(
    <BrowserRouter>
        <ul>
            <li>
                <NavLink to="/"
                    style={({ isActive }) => isActive ? activeStyle : {}}
                    className={({ isActive }) => isActive ? activeClassName : ''}
                >首页</NavLink>
            </li>
            <li><NavLink to="/user" >用户管理</NavLink></li>
            <li><NavLink to="/profile" >个人中心</NavLink></li>
        </ul>
        <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/user/*" element={<User />} >
                <Route path="add" element={<UserAdd />} />
                <Route path="list" element={<UserList />} />
                <Route path="detail/:id" element={<UserDetail />} />
            </Route>
            <Route path="/post/:id" element={<Post />} />
+           <Route path="/profile" element={<Protected component={Profile} />} />
+           <Route path="/login" element={<Login />} />
            <Route path="*" element={<Navigate to="/" />} />
        </Routes>
    </BrowserRouter>
    , document.getElementById('root'));

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';
-import Home from './components/Home';
-import User from './components/User';
-import UserAdd from './components/UserAdd';
-import UserList from './components/UserList';
-import UserDetail from './components/UserDetail';
-import Profile from './components/Profile';
-import Post from './components/Post';
-import Protected from './components/Protected';
-import Login from './components/Login';
+import routesConfig from './routesConfig';
+const activeStyle = { backgroundColor: "red" };
+const activeClassName = 'active';
+const LazyPost = React.lazy(() => import('./components/Post'));
+function App() {
+    let [routes, setRoutes] = React.useState(routesConfig);
+    const addRoute = () => {
+        setRoutes([...routes, {
+            path: '/foo', element: (
+                <React.Suspense fallback={<div>loading...</div>}>
+                    <LazyPost />
+                </React.Suspense>
+            )
+        }]);
+    }
+    return (
+        <div>
+            {useRoutes(routes)}
+            <button onClick={addRoute}>addRoute</button>
+        </div>
+    )
+}
ReactDOM.render(
    <BrowserRouter>
        <ul>
            <li>
                <NavLink to="/"
                    style={({ isActive }) => isActive ? activeStyle : {}}
                    className={({ isActive }) => isActive ? activeClassName : ''}
                >首页</NavLink>
            </li>
            <li><NavLink to="/user" >用户管理</NavLink></li>
            <li><NavLink to="/profile" >个人中心</NavLink></li>
        </ul>
+       <App />
    </BrowserRouter>
    , document.getElementById('root'));

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;