1.React RouterV6新特性 #

1.1 Switch重命名为Routes #

1.1.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
  <Router>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/user">User</Link></li>
      <li><Link to="/profile">Profile</Link></li>
    </ul>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user" element={<User />} />
      <Route path="/profile" element={<Profile />} />
    </Routes>
  </Router>,
  document.getElementById('root')
);

1.2 嵌套路由变得更简单 #

1.2.1 src\index.js #

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.render(
  <Router>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/user">User</Link></li>
      <li><Link to="/profile">Profile</Link></li>
    </ul>
    <Routes>
      <Route path="/" element={<Home />} />
+     <Route path="/user/*" element={<User />} />
      <Route path="/profile" element={<Profile />} />
    </Routes>
  </Router>,
  document.getElementById('root')
);

1.2.2 User.js #

src\components\User.js

import React from 'react';
+import { Route, Routes, Link } from 'react-router-dom';
+import UserAdd from './UserAdd';
+import UserDetail from './UserDetail';
+import UserList from './UserList';
function User() {
    return (
+       <div>
+           <ul>
+               <li><Link to="add">添加用户</Link></li>
+               <li><Link to="list">用户列表</Link></li>
+           </ul>
+           <Routes>
+               <Route path="add" element={<UserAdd />} />
+               <Route path="list" element={<UserList />} />
+               <Route path="detail/:id" element={<UserDetail />} />
+           </Routes>
+       </div>
+   )
}
export default User;

1.2.3 UserAdd.js #

src\components\UserAdd.js

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

1.2.4 UserDetail.js #

src\components\UserDetail.js

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

1.2.5 UserList.js #

src\components\UserList.js

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

1.3 Outlet #

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
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';
ReactDOM.render(
  <Router>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/user">User</Link></li>
      <li><Link to="/profile">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 />} />
    </Routes>
  </Router>,
  document.getElementById('root')
);

1.3.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="add">添加用户</Link></li>
                <li><Link to="list">用户列表</Link></li>
            </ul>
+           <Outlet />
        </div>
    )
}
export default User;

1.4 用useNavigate代替useHistory #

1.4.1 Home.js #

src\components\Home.js

import React from 'react';
import { useNavigate } from 'react-router-dom';
function Home() {
+   let navigate = useNavigate();
+   function navigateToUser() {
+       navigate('/user');
+   };
+   return (
+       <div>
+           <p>Home</p>
+           <button onClick={navigateToUser}>跳转到/user</button>
+       </div>
+   )
}
export default Home;

1.5 5. 新钩子useRoutes代替react-router-config #

1.5.1 src\index.js #

import React from 'react';
import ReactDOM from 'react-dom';
+import { BrowserRouter as Router, Route, Routes, Link, useRoutes } from 'react-router-dom';
+import routes from './routes';
+function App() {
+  return useRoutes(routes);
+}
ReactDOM.render(
  <Router>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/user">User</Link></li>
      <li><Link to="/profile">Profile</Link></li>
    </ul>
+   <App />
  </Router>,
  document.getElementById('root')
);

1.5.2 src\routes.js #

src\routes.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';
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 /> }
        ]
    },
    // 404找不到
    { path: '*', element: <NotFound /> }
];
export default routes;

1.6 Redirect 标签删除 #

import { HashRouter as Router, Route, Routes, Navigate } from 'react-router-dom'
<Router>
    <Routes>
        <Route path='/login' element={<Login/>}/>
        <Route path='/admin' element={<Admin/>}/>
        <Route path="*" element={<Navigate to="/login" />} />
    </Routes>
</Router>

2. React路由原理 #

2.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>

2.2 BrowserRouter #

2.2.1 history #

2.2.1.1 pushState #
2.2.1.2 onpopstate #
2.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>browser</title>
</head>

<body>
    <div id="root"></div>
    <ul>
        <li><a onclick="pushA()">/a</a></li>
        <li><a onclick="pushB()">/b</a></li>
        <li><a onclick="pushC()">/c</a></li>
        <li><a onclick="forward()">forward</a></li>
        <li><a onclick="back()">back</a></li>
        <li><a onclick="go(-1)">go-1=back</a></li>
        <li><a onclick="go(1)">go+1=forward</a></li>
    </ul>
    <script>
        let history = window.history;
        (function (history) {
            let oldPushState = history.pushState;
            history.pushState = function (state, title, pathname) {
                let result = oldPushState.apply(history, arguments);
                if (typeof window.onpushstate === 'function') {
                    window.onpushstate(new CustomEvent('pushstate', { detail: { pathname, state } }));
                }
            }
        })(history);
        window.onpushstate = (event) => {
            console.log(event);
            root.innerHTML = window.location.pathname;
        }
        window.onpopstate = (event) => {
            console.log(event);
            root.innerHTML = window.location.pathname;
        }
        function pushA() {
            history.pushState({ name: 'a' }, null, '/a');
        }
        function pushB() {
            history.pushState({ name: 'b' }, null, '/b');
        }
        function pushC() {
            history.pushState({ name: 'c' }, null, '/c');
        }
        function forward() {
            history.forward();
        }
        function back() {
            history.back();
        }
        function go(step) {
            history.go(step);
        }
    </script>
</body>
</html>

3.使用基本路由 #

3.1 安装 #

npm i react-router-dom --save

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

3.3 Home.js #

src\components\Home.js

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

3.4 User.js #

src\components\User.js

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

3.5 Profile.js #

src\components\Profile.js

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

4.实现基本路由 #

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

4.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 source = "^" + path;
    source += "$";
    let matcher = new RegExp(source);
    return matcher;
}
export function matchPath(path, pathname) {
    let matcher = compilePath(path);
    let match = pathname.match(matcher);
    if (!match) return null;
    return match;
}