React-Router V6
React 路由原理
- 不同的路径渲染不同的组件
- 有两种实现方式
- HashRouter:利用 hash 实现路由切换
- BrowserRouter:实现 h5 Api 实现路由的切换
HashRouter
HashRouter
- 利用 hash 实现路由切换
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>
// 当地址栏中路径中的hash值发生变化时,会执行回调函数
window.addEventListener('hashchange',()=>{
console.log(window.location.hash);
let pathname = window.location.hash.slice(1);//把最前面的那个#删除
root.innerHTML = pathname;
});
</script>
</body>
</html>
BrowserRouter
- 利用 h5 Api 实现路由的切换
history
浏览器的 history
对象是 JavaScript BOM(Browser Object Model)的一部分,它提供了与浏览器历史记录进行交互的方法和属性。这个对象可以让你在用户的浏览历史中移动,类似于在浏览器中点击前进和后退按钮。
注意,由于安全原因,大多数浏览器都限制了 history
对象的使用。例如,你不能查看用户的历史记录(只能知道历史记录的数量),也不能跨域修改历史记录。此外,pushState
和 replaceState
方法可能会受到浏览器的限制,防止你过于频繁地调用它们。
history.length
返回浏览器历史列表中的 URL 数量。这个数量包括当前页面。
history.back()
与在浏览器中点击后退按钮相同,使用户导航到前一个历史记录。
history.forward
与在浏览器中点击前进按钮相同,使用户导航到下一个历史记录。
history.go(n)
使用户导航到他们的历史记录中的特定点。这个 n
参数是一个整数,表示相对于当前页面的位置。例如,history.go(1)
相当于 history.forward()
,history.go(-1)
相当于 history.back()
。
history.pushState(state, title, url)
向历史堆栈添加一个新的状态和 URL,而不会刷新页面。state
参数是一个与添加的历史记录相关联的状态对象,title
参数是新页面的标题(但大多数浏览器目前都忽略这个参数),url
参数是新历史记录的 URL。
history.replaceState(state, title, url)
更新当前历史记录的状态对象、标题和 URL,而不需要重新加载页面。参数的含义与 pushState
方法相同。
案例
- 浏览器针对每个页面维护一个
History
栈,执行pushState
函数可压入设定的url
至栈顶,同时修改当前指针 - 当执行
back
和forward
操作时,history 栈大小并不会改变(history.length 不变),仅仅移动当前指针的位置 - 若当前指针在 history 栈的中间位置(非栈顶),此时执行 pushState 会在指针当前的位置添加此条目,并成为新的栈顶
<!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;
padding: 10px;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
var historyObj = window.history;
var root = document.getElementById("root");
function updateContent(pathname) {
root.innerHTML = pathname;
}
window.addEventListener("popstate", (event) => {
updateContent(window.location.pathname);
});
(function (historyObj) {
let oldPushState = history.pushState;
historyObj.pushState = function (state, title, pathname) {
let result = oldPushState.apply(history, arguments);
updateContent(pathname);
return result;
};
})(historyObj);
setTimeout(
() => historyObj.pushState({ page: 1 }, "page1", "/page1"),
1000
);
setTimeout(
() => historyObj.pushState({ page: 2 }, "page2", "/page2"),
2000
);
setTimeout(
() => historyObj.pushState({ page: 3 }, "page3", "/page3"),
3000
);
setTimeout(() => historyObj.back(), 4000);
setTimeout(
() => historyObj.pushState({ page: 4 }, "page4", "/page4"),
5000
);
setTimeout(() => historyObj.go(1), 6000);
</script>
</body>
</html>
使用基本路由
- https://create-react-app.dev
- https://reactrouter.com/docs/en/v6
- https://www.reactrouter.cn/
- https://github.com/remix-run/react-router
安装
react-router-dom
是 React 中一个非常重要的库,它可以用于在你的 React 应用程序中添加路由功能。基本上,你可以使用它在你的应用程序中的不同部分之间导航,就像在不同的页面之间导航一样,但不需要重新加载整个应用程序。
HashRouter
:和BrowserRouter
类似,HashRouter
是一个顶层的路由组件,它会为你的应用程序创建一个路由环境。不同之处在于,HashRouter
使用 URL 的 hash(#)部分来保持 UI 和 URL 的同步。它在历史记录管理上,所有的页面跳转、后退都可以通过 hash 实现,可以用于那些不能使用 HTML5 history API 的老式浏览器。Routes
:在React Router v6中,Routes
取代了v5中的Switch
组件。它用于包含多个Route
组件,并且只渲染与当前路径最匹配的Route
。注意,在 v6 中,Routes
和Route
的使用方式有所变化,所有的Route
都需要作为Routes
的子组件。Route
:这是最基本的组件,用于在应用程序中定义不同的路由。每个Route
都有一个path
属性,表示当 URL 与此路径匹配时应该渲染哪个组件。
在这个例子中,首先我们使用 HashRouter
包裹我们的应用程序。然后我们定义了 Routes
组件,所有的 Route
组件都被包含在其中。注意 Route
组件的新用法,我们将要渲染的组件作为 element
属性传递进去。当 URL 的路径与 Route
的 path
属性匹配时,就会渲染对应的 element
。
npm i react-router-dom history --save
src\index.js
Routes就是React-Router V5中的switch,有一组匹配上了,后面的就不匹配了
// 引入React核心库
import React from "react";
// 引入ReactDOM,用于DOM操作
import ReactDOM from "react-dom/client";
// 引入react-router-dom中的HashRouter, BrowserRouter, Routes, Route组件
import { HashRouter, BrowserRouter, Routes, Route } from "./react-router-dom";
// 引入Home组件
import Home from "./components/Home";
// 引入User组件
import User from "./components/User";
// 引入Profile组件
import Profile from "./components/Profile";
// 创建并渲染根组件
ReactDOM.createRoot(document.getElementById("root")).render(
// 使用HashRouter包裹整个应用,实现哈希路由功能
<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>
);
这段代码包括了React的引入,React DOM的引入,以及react-router-dom
中的路由组件的引入。接着,它引入了三个自定义组件(Home、User、Profile),并使用ReactDOM.createRoot
来渲染一个包含了HashRouter
和Routes
的React组件树。这为应用程序提供了基于哈希的路由功能,允许应用程序根据URL的不同部分来显示不同的组件。
Home.js
src\components\Home.js
import React from "react";
function Home(props) {
console.log(props);
return <div>Home</div>;
}
export default Home;
User.js
src\components\User.js
import React from "react";
function User() {
return <div>User</div>;
}
export default User;
Profile.js
src\components\Profile.js
import React from "react";
function Profile() {
return <div>Profile</div>;
}
export default Profile;
实现基本路由
react-router-dom\index.js
src\react-router-dom\index.js
// 导入React
import React from "react";
// 从react-router中导入Router组件
import { Router } from "../react-router";
// 从history库中导入createHashHistory和createBrowserHistory函数
import { createHashHistory, createBrowserHistory } from "history";
// 导出react-router中的所有内容
export * from "../react-router";
// 定义一个HashRouter函数组件,接收children作为props
export function HashRouter({ children }) {
// 使用useRef创建一个可变的ref对象,用于存储历史对象的引用
let historyRef = React.useRef();
// 如果historyRef的current属性为null,表示尚未创建历史对象
if (historyRef.current == null) {
// 创建一个哈希历史对象并赋值给historyRef的current属性
historyRef.current = createHashHistory();
}
// 获取当前的历史对象
let history = historyRef.current;
// 使用useState创建一个状态,存储当前的动作和位置
let [state, setState] = React.useState({
action: history.action,
location: history.location,
});
//使用useLayoutEffect在历史对象发生变化时更新状态
React.useLayoutEffect(() => history.listen(setState), [history]);
// 渲染Router组件,并传递children、位置、导航类型和历史对象
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
// 定义一个BrowserRouter函数组件,接收children作为props
export function BrowserRouter({ children }) {
// 使用useRef创建一个可变的ref对象,用于存储历史对象的引用
let historyRef = React.useRef();
// 如果historyRef的current属性为null,表示尚未创建历史对象
if (historyRef.current == null) {
// 创建一个浏览器历史对象并赋值给historyRef的current属性
historyRef.current = createBrowserHistory();
}
// 获取当前的历史对象
let history = historyRef.current;
// 使用useState创建一个状态,存储当前的动作和位置
let [state, setState] = React.useState({
action: history.action,
location: history.location,
});
// 使用useLayoutEffect在历史对象发生变化时更新状态
React.useLayoutEffect(() => history.listen(setState), [history]);
//渲染Router组件,并传递children、位置、导航类型和历史对象
return (
<Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
这段代码定义了两个 React 组件:HashRouter
和BrowserRouter
。这两个组件都利用react-router
和history
库来创建和管理路由。HashRouter
使用 URL 的哈希部分来处理路由,而BrowserRouter
使用 HTML5 的历史 API。两者都通过监听历史对象的变化来更新路由状态,并将更新的状态传递给Router
组件以实现路由导航。这种方式为 React 应用程序提供了灵活的路由管理能力。
src\react-router\index.js
src\react-router\index.js
// 导入React库
import React from "react";
// 创建导航上下文
const NavigationContext = React.createContext({});
// 创建位置上下文
const LocationContext = React.createContext({});
// 导出上下文
export { NavigationContext, LocationContext };
// 定义Router函数组件
export function Router({ children, location, navigator }) {
// 使用useMemo钩子创建导航上下文对象
const navigationContext = React.useMemo(() => ({ navigator }), [navigator]);
// 使用useMemo钩子创建位置上下文对象
const locationContext = React.useMemo(() => ({ location }), [location]);
// 返回Router组件结构
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider value={locationContext} children={children} />
</NavigationContext.Provider>
);
}
// 定义Routes函数组件
export function Routes({ children }) {
// 使用useRoutes钩子创建路由
return useRoutes(createRoutesFromChildren(children));
}
// 定义useLocation钩子
export function useLocation() {
// 返回当前位置上下文的位置信息
return React.useContext(LocationContext).location;
}
// 定义useSearchParams钩子
export function useSearchParams() {
// 从位置上下文中获取当前位置
const location = React.useContext(LocationContext).location;
const pathname = location.pathname;
// 返回当前URL的查询参数
return new URLSearchParams(pathname.split("?")[1]);
}
// 定义useRoutes钩子
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提供的一个工具方法,可帮助遍历子元素,可以同时兼容单子元素,子元素数组
React.Children.forEach(children, (element) => {
let route = {
path: element.props.path,
element: element.props.element,
};
routes.push(route);
});
return routes;
}
// 定义Route函数组件(空实现)
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;
}
这段代码是一个React路由管理工具的实现,包括创建上下文(NavigationContext
和LocationContext
),定义路由器(Router
)和路由(Routes
,Route
),以及一些自定义钩子(useLocation
, useSearchParams
, useRoutes
)用于管理路由状态和解析URL参数。核心功能是根据React的子组件定义动态路由,以及提供路径匹配功能。代码风格注重模块化和可复用性,适合用于构建复杂的单页应用程序。
实现 history
createBrowserHistory.js
src\history\createBrowserHistory.js
// 定义 createBrowserHistory 函数
function createBrowserHistory() {
// 获取全局历史对象
const globalHistory = window.history;
// 定义状态变量
let state;
// 初始化监听器数组
let listeners = [];
// 添加历史监听器
function listen(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter(item => item !== listener);
};
}
// 前进或后退指定的历史记录条数
function go(n) {
globalHistory.go(n);
}
// 后退一条历史记录
function goBack() {
globalHistory.go(-1);
}
// 前进一条历史记录
function goForward() {
globalHistory.go(1);
}
// 添加新的历史记录
function push(pathname, nextState) {
const action = 'PUSH';
if (typeof pathname === 'object') {
state = pathname.state;
pathname = pathname.pathname;
} else {
state = nextState;
}
globalHistory.pushState(state, null, pathname);
let location = { pathname, state };
notify({ action, location });
}
// 替换当前历史记录
function replace(pathname, nextState) {
const action = 'REPLACE';
if (typeof pathname === 'object') {
state = pathname.state;
pathname = pathname.pathname;
} else {
state = nextState;
}
globalHistory.replaceState(state, null, pathname);
let location = { pathname, state };
notify({ action, location });
}
// 监听浏览器历史变化
window.addEventListener('popstate', () => {
let location = {
state: globalHistory.state,
pathname: window.location.pathname
};
notify({ action: 'POP', location });
});
// 通知所有监听器
function notify({ action, location }) {
history.action = action;
history.location = location;
history.length = globalHistory.length;
listeners.forEach(listener => {
listener({ action, location });
});
}
// 初始化 history 对象
const history = {
action: 'POP',
go,
goBack,
goForward,
push,
replace,
listen,
location: {
pathname: window.location.pathname,
state: globalHistory.state
}
};
// 返回 history 对象
return history;
}
// 导出 createBrowserHistory 函数
export default createBrowserHistory;
createBrowserHistory
函数用于创建一个自定义的浏览器历史管理对象。这个对象允许应用程序监听和操作浏览器的历史记录。它提供了几个关键功能:添加新的历史记录(push
)、替换当前记录(replace
)、前进和后退(go
、goBack
、goForward
)、以及订阅历史变化事件(listen
)。这个函数利用了浏览器的 History API
和 popstate
事件来实现这些功能。
createHashHistory.js
src\history\createHashHistory.js
// 定义一个函数 createHashHistory
function createHashHistory() {
// 声明一个栈来存储历史记录
let stack = [];
// 当前位置的索引
let index = -1;
// 当前动作(默认为'POP')
let action = 'POP';
// 当前的状态
let state;
// 监听函数数组
let listeners = [];
// 添加一个监听函数
function listen(listener) {
// 将监听器添加到数组中
listeners.push(listener);
// 返回一个函数,用于移除监听器
return () => {
listeners = listeners.filter(item => item !== listener);
};
}
// 添加一个新的历史记录
function push(pathname, nextState) {
// 设置动作为'PUSH'
action = 'PUSH';
// 检查 pathname 是否为对象
if (typeof pathname === 'object') {
// 从对象中获取 state 和 pathname
state = pathname.state;
pathname = pathname.pathname;
} else {
// 使用提供的 nextState
state = nextState;
}
// 更新浏览器的哈希值
window.location.hash = pathname;
}
// 移动到历史记录中的某个位置
function go(n) {
// 设置动作为'POP'
action = 'POP';
// 更新索引
index += n;
// 获取新位置的历史记录
let nextLocation = stack[index];
// 更新状态
state = nextLocation.state;
// 更新浏览器的哈希值
window.location.hash = nextLocation.pathname;
}
// 后退
function goBack() {
// 调用 globalHistory.go 函数后退一步
globalHistory.go(-1);
}
// 前进
function goForward() {
// 调用 globalHistory.go 函数前进一步
globalHistory.go(1);
}
// 处理哈希值变化的函数
function handleHashChange() {
// 获取新的 pathname
const pathname = window.location.hash.slice(1);
// 更新历史对象的 action
history.action = action;
// 构建新的 location 对象
const location = {
pathname,
state
};
// 更新历史对象的 location
history.location = location;
// 如果是 PUSH 动作,更新栈和索引
if (action === 'PUSH') {
stack[++index] = location;
}
// 通知所有监听器
listeners.forEach(listener => {
listener({
action,
location
});
});
}
// 监听 hashchange 事件
window.addEventListener('hashchange', handleHashChange);
// 创建一个历史对象
const history = {
action: 'POP',
go,
goBack,
goForward,
push,
listen,
location: {
pathname: undefined,
state: undefined
}
};
// 初始化历史对象
if (window.location.hash) {
action = 'PUSH';
handleHashChange();
} else {
window.location.hash = '/';
}
// 返回创建的历史对象
return history;
}
// 导出 createHashHistory 函数
export default createHashHistory;
createHashHistory
函数创建了一个自定义的历史对象,用于管理网页应用中的导航状态。- 使用栈结构来存储历史记录,通过
push
和go
方法来添加和移动历史记录。 listen
方法允许其他部分的代码注册监听器,这些监听器在历史记录改变时被调用。handleHashChange
方法监听浏览器的hashchange
事件,以响应 URL 的哈希部分的变化。- 最后,函数返回这个自定义历史对象,允许其他代码通过这个对象
- react-router-dom V5 里当使用HashRouter的时候,如果没有默认hash会添加默认hash
- 但是在react-router-dom V6里,里当使用HashRouter的时候,如果没有默认hash不会添加默认hash
history\index.js
src\history\index.js
export { default as createBrowserHistory } from "./createBrowserHistory";
export { default as createHashHistory } from "./createHashHistory";
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}
/>
);
}
path-to-regexp
/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"));
这个正则表达式 /^\/home[\/#\?]?$/i
用于匹配特定格式的字符串,具体解释如下:
^
:表示字符串的开始。这个符号确保匹配必须从字符串的开始处进行。\/
:匹配斜杠/
。在正则表达式中,斜杠是一个特殊字符,因此它前面有一个反斜杠用作转义字符。home
:直接匹配文本 “home”。[\/#\?]
:这是一个字符集,用于匹配集合中的任意一个字符。\/
:匹配一个斜杠/
。#
:匹配井号#
。\?
:匹配问号?
。问号也是正则表达式中的特殊字符,所以需要用反斜杠进行转义。
?
:表示前面的字符集[\/#\?]
是可选的,即这个字符集中的字符出现零次或一次。$
:表示字符串的结束。这个符号确保匹配必须在字符串的末尾结束。i
:这是一个修饰符,表示匹配时不区分大小写。
综上所述,这个正则表达式用于匹配以下类型的字符串:
- 它必须以
/home
开头。 /home
后面可以紧跟一个/
、#
或?
中的任意一个字符,但这个字符是可选的。- 字符串必须以
/home
或/home
后紧跟的那个可选字符结束。 - 匹配时不区分大小写。
例如,它可以匹配 /home
、/Home
、/home/
、/home#
或 /home?
,但不会匹配 /homepage
或 /home/123
。
/home 非结束
let {pathToRegexp} = require("path-to-regexp");
let regExp = pathToRegexp("/home", [], { end: false });
console.log(regExp);
// /^\/home(?:[\/#\?](?=[]|$))?(?=[\/#\?]|[]|$)/i
console.log(regExp.exec("/home"));
console.log(regExp.exec("/home/"));
console.log(regExp.exec("/home//"));
console.log(regExp.exec("/home/2"));
console.log(regExp.exec("/home#"));
console.log(regExp.exec("/home#top"));
console.log(regExp.exec("/home?"));
console.log(regExp.exec("/home?a=1"));
console.log(/^\/home(?:[\/#\?](?=[]|$))?/.exec("/home?a=1"));
这个正则表达式用于匹配特定的 URL 路径。让我们分解并理解它的每个部分:
^
: 这个字符表示正则表达式的开始。它确保匹配必须从字符串的开头开始。\/home
: 这部分匹配字符串 “/home”。斜杠 (/
) 被转义(即前面有一个反斜杠\
),这是因为在正则表达式中,斜杠是一个特殊字符,需要转义来表示它的字面值。(?:[\/#\?](?=[]|$))?
: 这是一个非捕获组(由(?: ... )
表示),它用于对一组字符进行分组,但不保存该分组用于后续的引用。这个组的内容是:[\/#\?]
: 这是一个字符集,匹配一个字符,可以是/
、#
或者?
。(?=[]|$)
: 这是一个零宽正向前瞻断言,表示接下来的位置必须是正则表达式[]|$
的起始位置。由于[]
是一个空的字符集,所以这实际上等同于只检查$
(字符串的结尾)。?
: 表示前面的非捕获组是可选的。
(?=[\/#\?]|[]|$)
: 这又是一个零宽正向前瞻断言,用于确保当前位置后面是[/#?]
中的一个字符,或者是字符串的结尾。i
: 这是一个修饰符,表示正则表达式的匹配是不区分大小写的。
这个正则表达式用于匹配以 /home
开头的字符串,后面可以选择性地跟着一个 /
、#
或 ?
字符。如果有这些字符之一,它们必须是字符串的结尾或者紧跟着字符串的结尾。由于使用了 i
修饰符,所以匹配是不区分大小写的。
这种模式通常用于路由匹配,如在 web 应用中检测是否导航到了 “/home” 路径,且路径可能附带查询参数或哈希值。
路径参数
let params = [];
let regExp = pathToRegExp("/user/:id", params, { end: true });
console.log(regExp, params);
/**
/^\/user\/(?:([^\/]+?))\/?$/i
[ { name: 'id', optional: false, offset: 7 } ]
**/
正则匹配
- 是否捕获
表达式 | 含义 |
---|---|
() | 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n 是一个数字,表示第 n 个捕获组的内容) |
(?
标签:function,return,React,let,location,V6,Router,const
From: https://blog.csdn.net/qq_40588441/article/details/141829055
相关文章
|