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;
        }it 
    </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);
        // 获取哈希值,并去掉前面的'#'符号,赋值给pathname变量
        let pathname = window.location.hash.slice(1);
        // 将pathname的值设置为root元素的内容
        root.innerHTML = pathname;
    });
    </script>
</body>
</html>

1.2 BrowserRouter #

1.2.1 history #

浏览器的 history 对象是 JavaScript BOM(Browser Object Model)的一部分,它提供了与浏览器历史记录进行交互的方法和属性。这个对象可以让你在用户的浏览历史中移动,类似于在浏览器中点击前进和后退按钮。

注意,由于安全原因,大多数浏览器都限制了 history 对象的使用。例如,你不能查看用户的历史记录(只能知道历史记录的数量),也不能跨域修改历史记录。此外,pushStatereplaceState 方法可能会受到浏览器的限制,防止你过于频繁地调用它们。

1.2.1.1 history.length #

返回浏览器历史列表中的 URL 数量。这个数量包括当前页面。

1.2.1.2 history.back() #

与在浏览器中点击后退按钮相同,使用户导航到前一个历史记录。

1.2.1.3 history.forward #

与在浏览器中点击前进按钮相同,使用户导航到下一个历史记录。

1.2.1.4 history.go(n) #

使用户导航到他们的历史记录中的特定点。这个 n 参数是一个整数,表示相对于当前页面的位置。例如,history.go(1) 相当于 history.forward()history.go(-1) 相当于 history.back()

1.2.1.5 history.pushState(state, title, url) #

向历史堆栈添加一个新的状态和 URL,而不需要重新加载页面。state 参数是一个与添加的历史记录相关联的状态对象,title 参数是新页面的标题(但大多数浏览器目前都忽略这个参数),url 参数是新历史记录的 URL。

1.2.1.6 history.replaceState(state, title, url) #

更新当前历史记录的状态对象、标题和 URL,而不需要重新加载页面。参数的含义与 pushState 方法相同。

1.2.2 案例 #

<!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>
    // 定义一个渲染函数,将当前的路径名设置为root元素的内容
    function render() {
        root.innerHTML = window.location.pathname;
    }
    // 当浏览器历史记录发生变化时,调用render函数
    window.onpopstate = render;
    // 获取浏览器的history对象
    let historyObj = window.history;
    // 保存原始的pushState方法
    let oldPushState = historyObj.pushState;
    // 重写history对象的pushState方法
    historyObj.pushState = function (state, title, url) {
        // 调用原始的pushState方法
        oldPushState.apply(history, arguments);
        // 调用render函数
        render();
    }
    // 定义一个go函数,用于改变当前的路径
    function go(path) {
        historyObj.pushState({}, null, path);
    }
    // 定义一个forward函数,用于前进到下一个历史记录
    function forward() {
        historyObj.go(1);
    }
    // 定义一个back函数,用于后退到上一个历史记录
    function back(path) {
        historyObj.go(-1);
    }
  </script>
</body>

</html>

2.使用基本路由 #

2.1 安装 #

react-router-dom 是 React 中一个非常重要的库,它可以用于在你的 React 应用程序中添加路由功能。基本上,你可以使用它在你的应用程序中的不同部分之间导航,就像在不同的页面之间导航一样,但不需要重新加载整个应用程序。

  1. HashRouter:和 BrowserRouter 类似,HashRouter 是一个顶层的路由组件,它会为你的应用程序创建一个路由环境。不同之处在于,HashRouter 使用 URL 的 hash(#)部分来保持 UI 和 URL 的同步。它在历史记录管理上,所有的页面跳转、后退都可以通过 hash 实现,可以用于那些不能使用 HTML5 history API 的老式浏览器。

  2. Routes:在React Router v6中,Routes取代了v5中的Switch组件。它用于包含多个 Route 组件,并且只渲染与当前路径最匹配的 Route。注意,在 v6 中,RoutesRoute 的使用方式有所变化,所有的 Route 都需要作为 Routes 的子组件。

  3. Route:这是最基本的组件,用于在应用程序中定义不同的路由。每个 Route 都有一个 path 属性,表示当 URL 与此路径匹配时应该渲染哪个组件。

在这个例子中,首先我们使用 HashRouter 包裹我们的应用程序。然后我们定义了 Routes 组件,所有的 Route 组件都被包含在其中。注意 Route 组件的新用法,我们将要渲染的组件作为 element 属性传递进去。当 URL 的路径与 Routepath 属性匹配时,就会渲染对应的 element

npm i react-router-dom --save

2.2 src\index.js #

// 导入 React 库
import React from 'react';
// 导入 ReactDOM 库,它提供 DOM 相关的功能
import ReactDOM from 'react-dom/client';
// 导入 HashRouter, Routes, Route 组件,它们用于实现路由功能
import { HashRouter, Routes, Route } from 'react-router-dom';
// 导入 Home、User、Profile 组件,这些组件将用于渲染不同的页面
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
// 创建一个 JSX 元素,该元素使用 HashRouter 和 Routes 定义了路由
let element = (
 <HashRouter>
    <Routes>
      // 当 URL 的路径为 "/" 时,渲染 Home 组件
      <Route path="/" element={<Home />} />
      // 当 URL 的路径为 "/user" 时,渲染 User 组件
      <Route path="/user" element={<User />} />
      // 当 URL 的路径为 "/profile" 时,渲染 Profile 组件
      <Route path="/profile" element={<Profile />} />
    </Routes>
  </HashRouter>
)
// 通过 document.getElementById 获取页面中的 root 元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 使用 ReactDOM 的 render 方法,将我们定义的路由元素渲染到 root 元素中
root.render(element);

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. 路径参数 #

你可以在路由的 path 属性中定义参数,然后在相应的组件中使用这些参数。参数在路径中定义为冒号后跟参数名称,例如 :id。

3.1 src\index.js #

import React from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter, 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';
let element = (
 <HashRouter>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/user" element={<User />} />
      <Route path="/profile" element={<Profile />} />
+     <Route path="/post/:id" element={<Post />} />
    </Routes>
  </HashRouter>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

3.2 Post.js #

src\components\Post.js

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

4. Link导航 #

Link 组件用于创建导航链接。它类似于 HTML 中的 <a> 标签,但是它使用 JavaScript 进行页面切换,这样可以避免页面的全局刷新。

每个 Link 组件都有一个 to 属性,表示该链接指向的路径。当用户点击 Link 组件时,应用程序的 URL 将更新为该 to 属性指定的路径,同时将渲染与新 URL 匹配的 <Route> 组件。

注意,Link 组件必须在 BrowserRouterHashRouter 组件(或者其他提供路由 context 的组件)的内部使用,因为它依赖于 React Router 提供的路由 context。如果你尝试在没有路由 context 的地方使用 Link 组件,React 会抛出一个错误。

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+import { HashRouter, 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';
let element = (
 <HashRouter>
+   <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>
  </HashRouter>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

5. 支持嵌套路由和Outlet #

嵌套路由允许你在一个组件内部定义其他的路由,这样你就可以根据 URL 的不同部分显示不同的 UI,而不需要在一个地方定义所有的路由。

Outlet 组件用于表示子路由应该被渲染的位置。你可以把它理解为一个占位符,它在你的组件树中标记了子路由组件应该出现的位置。

User 组件使用了 Outlet 组件。这意味着任何在 Routes 组件内部与当前路径匹配的子 Route 都会在 Outlet 组件的位置被渲染

5.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+import { 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';
+import UserAdd from './components/UserAdd';
+import UserList from './components/UserList';
+import UserDetail from './components/UserDetail';
let element = (
+   <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>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

5.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;

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

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

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

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

NavLink 是一个特殊类型的 Link,它知道自己是否指向当前页面,如果是的话,它可以自动为自己添加一个 "active" 类名或样式。

NavLink 组件有一个 className 属性,你可以用它来添加一个或多个类名。然后,当 URL 匹配到这个链接的 to 属性时,NavLink 将自动添加 active 类名。

此外,NavLink 还有一个 activeStyle 属性,你可以用它来定义当链接处于活动状态时应用的内联样式。

每个 NavLink 组件都有一个 to 属性,表示该链接指向的路径,还有一个 activeClassName 属性,表示当该链接处于活动状态时,应该添加的类名。当用户点击 NavLink 组件时,应用程序的 URL 将更新为该 to 属性指定的路径,同时如果新的 URL 与 to 属性匹配,那么就会添加 activeClassName 属性指定的类名。

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

6.2 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, Routes, Route,NavLink } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
+import {activeNavProps} from './utils';
let element = (
    <BrowserRouter>
        <ul>
+        <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
+        <li><NavLink to="/user" {...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>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

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

+const activeStyle = { backgroundColor: 'green' };
+const activeClassName = 'active';
+export const activeNavProps = {
+  style: ({ isActive }) => isActive ? activeStyle : {},
+  className: ({ isActive }) => isActive ? activeClassName : ''
+}

7. 跳转和重定向 #

Navigate 是 React Router v6 中用于执行导航操作的组件。你可以用它来实现在组件中的导航,例如在用户登录后跳转到主页,或者在表单提交后返回上一页等。

Navigate 组件有一个 to 属性,用来指定要导航到的路径。

此外,Navigate 还有一个 state 属性,你可以用它传递一些状态数据到新的位置。这些数据可以在目标位置通过 useLocation hook 的 state 属性获取。

使用 Navigate,你可以在组件中方便地执行导航操作,而无需手动操作历史堆栈。

7.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, Routes, Route,NavLink ,Navigate} from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import {activeNavProps} from './utils';
let element = (
    <BrowserRouter>
        <ul>
         <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
         <li><NavLink to="/user" {...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>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

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

8. 受保护路由 #

8.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route,NavLink ,Navigate} from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import {activeNavProps} from './utils';
+import Protected from './components/Protected';
+import Login from './components/Login';
let element = (
    <BrowserRouter>
        <ul>
         <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
         <li><NavLink to="/user" {...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>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

8.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;

8.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;

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

React 的 Suspense 组件是一种控制组件加载状态的机制。在 React 加载和渲染异步数据时,Suspense 可以提供一个"fallback"渲染,这个 fallback 渲染可以是一个加载指示符或者其它任何你想展示的内容,直到真正的组件准备好渲染。

Post 使用 React.lazy 进行动态导入,因此它是一个异步组件。在 OtherComponent 加载和渲染完成之前,Suspense 组件将会渲染其 fallback 属性所指定的内容。

9.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, NavLink, useRoutes } from 'react-router-dom';
import {activeNavProps} from './utils';
+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>
+    )
+}
let element = (
    <BrowserRouter>
        <ul>
         <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
         <li><NavLink to="/user" {...activeNavProps}>用户管理</NavLink></li>
         <li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
        </ul>
        <App/>
    </BrowserRouter>
)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);

9.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;

9.3 NotFound.js #

src\components\NotFound.js

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