4-4、React 路由管理

路由的演变

之前,部署到服务器的前端项目是由多个 HTML 文件组成,每个 HTML 都有对应服务器路径,前端称其为路由,路由之间使用location.href跳转,跳转路径就是另一个 HTML 的服务器地址。这时候的路由是由后端来管理的
后面单页应用流行,部署到服务器的前端项目就只有一个 HTML 文件,对应一个服务器路径。这时候为满足不同页面的展示,就需要借助框架提供的路由能力,至此路由的管理转移到前端身上。

路由的组成

location的组成:
location.protocal协议
location.host 域名
location.port 端口(多数省略了)
location.pathname 路径
location.search 参数,[? 后面,# 之前)的内容
location.hash 锚点,# 后面的内容

路由的分类

单页应用下,分为:hash、history
hash:
路由上带 #,内容为 # 后面,用它来区分页面;
不需要服务端配合。

history:
路由上不带 #,内容为[域名后面,? 之前),用它来区分页面;
需要服务端配合。因为部署到服务器后,该模式实际上访问服务器的资源,但单页应用只有一个指向 html 的路径,所以这样访问会返回 404,一般需要配置让其指向 html 的路径

路由实现的核心原理

核心原理:监听路径的变化,找到该路径对应的组件,然后渲染到相应位置,并注入 router 等上下文。其中的对应关系就是我们常写的路由配置项。

react-router

官网:React Router

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import "./App.css";

import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";

const Menu = () => (
<div>
<header>
<ul style={{ display: "flex" }}>
<a href="/">首页</a>
<span style={{ margin: "0 10px" }}>|</span>
<a href="/list">新闻列表</a>
<span style={{ margin: "0 10px" }}>|</span>
<a href="/about">关于我们</a>
</ul>
</header>
<Outlet />
</div>
);

function App() {
return (
<BrowserRouter>
<Menu />
<Routes>
<Route path="/" element={<div>首页 page</div>}></Route>
<Route path="/list" element={<div>新闻列表 page</div>}></Route>
<Route path="/about" element={<div>关于我们 page</div>}></Route>
</Routes>
</BrowserRouter>
);
}

export default App;

基本原理(手撸简版)

简单手撸react-router-dom核心原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
<Routes>
<Route path="/" element={<Menu />}>
<Route path="/" element={<div>首页 page</div>}></Route>
<Route path="/list" element={<div>新闻列表 page</div>}></Route>
<Route path="/about" element={<div>关于我们 page</div>}></Route>
</Route>
</Routes>
*/

// 上述代码 等价于:

/*
const routes = [
{
path: "/",
element: <Menu />,
children: [
{
path: "/",
element: <div>首页 page</div>,
},
{
path: "/list",
element: <div>新闻列表 page</div>,
},
{
path: "/about",
element: <div>关于我们 page</div>,
},
],
},
];

const Routeing = useRoutes(routes);
*/

import React from "react";

const LocationContext = React.createContext({});
const NavigationContext = React.createContext({});

/**
* BrowserRouter 是一个基于 React 的路由器组件,用于在浏览器中导航。
* 它接收一个 children 属性,该属性是一个 React 元素,表示要渲染的组件。
* 它返回一个包含 LocationContext 和 NavigationContext 的组件,这两个上下文提供了有关当前位置和导航器的信息。
*
* @param {object} props - 包含 children 属性的对象。
* @returns {ReactElement} - 返回一个包含 LocationContext 和 NavigationContext 的组件。
*/
export function BrowserRouter({ children }) {
return (
// 创建一个 LocationContext.Provider 组件,并设置其值为一个对象,该对象包含 location 属性,值为 window.location
<LocationContext.Provider value={{ location: window.location }}>
<NavigationContext.Provider value={{ navigator: window.history }}>
{children}
</NavigationContext.Provider>
</LocationContext.Provider>
);
}

export function useLocation() {
return React.useContext(LocationContext).location;
}

export function useNavigation() {
return React.useContext(NavigationContext).navigator;
}

export function findRoute(routes, pathname) {
routes.find(({ path }) => path === pathname);
return;
}

/**
* 使用路由
*
* @param routes 路由列表
* @returns 父级路由的元素,与路由匹配到的渲染组件
*/
export function useRoutes(routes) {
// 获取当前位置信息
const location = useLocation();
// 获取当前路径
const pathname = location.pathname || "/";
// 在路由列表中查找当前路径对应的路由
const parentRoute = findRoute(routes, pathname);

// 返回父级路由的元素
return parentRoute?.element;
}

/**
* 将子节点转换为路由对象数组
*
* @param children 子节点
* @returns 路由对象数组
*/
export function childrenToRoutes(children) {
const routes = [];

// 遍历子节点
React.Children.forEach(children, (node) => {
// 提取节点路径和元素
const { path, element } = node.props;

// 构建路由对象
const route = { path, element };

// 如果节点有子节点,递归调用childrenToRoutes函数
if (node.props.children) {
route.children = childrenToRoutes(node.props.children);
}

// 将路由对象添加到routes数组中
routes.push(route);
});

// 返回routes数组
return routes;
}

/**
* 定义路由组件
*
* @param children 组件列表
* @returns 使用路由组件返回结果
*/
export function Routes({ children }) {
return useRoutes(childrenToRoutes(children));
}

手撸 Router

核心原理:监听路径的变化,找到该路径对应的组件,然后渲染到相应位置,并注入 router 等上下文。其中的对应关系就是我们常写的路由配置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>base-index-html</title>
</head>
<body>
<div>
<nav>
<a href="#">首页</a>
<a href="#about">关于我们</a>
<a href="#list">新闻列表</a>
<a href="#post">新闻详情</a>
</nav>
<section>
<div class="router-view"></div>
</section>
</div>
<script>
class Router {
constructor(routes) {
this.routes = routes;

this.init();
}

init() {
window.addEventListener("hashchange", () => this.onHashChange());

window.addEventListener("load", () => this.onHashChange());
}

onHashChange() {
const hash = window.location.hash.slice(1);

const route = this.findRoute(hash);

this.updateView(route);
}

findRoute(hash) {
return this.routes.find((route) => route.path === "/" + hash);
}

updateView(route) {
const viewEle = document.querySelector(".router-view");

viewEle.innerHTML = route ? route.element : "404";
}

push(path) {
window.location.hash = path.slice(1);
}
}

const routes = [
{
path: "/",
element: `
<div>
<div>首页 page</div>
<button onclick="router.push('/about')">去 about</button>
</div>
`,
},
{
path: "/list",
element: `
<div>
<div>新闻列表 page</div>
</div>
`,
},
{
path: "/about",
element: `
<div>
<div>关于我们 page</div>
</div>
`,
},
{
path: "/post",
element: `
<div>
<div>新闻详情 page</div>
</div>
`,
},
];
const router = new Router(routes);
</script>
</body>
</html>

4-4、React 路由管理
https://mrhzq.github.io/职业上一二事/前端面试/前端八股文/4-4、React 路由管理/
作者
黄智强
发布于
2024年1月13日
许可协议