跳到主要内容

状态管理

router

React Router 是一个流行的第三方库,它允许在 React 应用程序中实现路由功能。React Router 支持两种路由方式:HashRouter 和 BrowserRouter。

  1. HashRouter

HashRouter 使用 URL 中的 hash 部分(即 #)来实现路由。在 React 中,可以使用 <HashRouter> 组件来创建 HashRouter。例如:

jsxCopy codeimport { HashRouter, Route, Link } from 'react-router-dom';

function App() {
return (
<HashRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</HashRouter>
);
}

在使用 HashRouter 时,URL 中的路径看起来像这样:http://example.com/#/about。HashRouter 不会向服务器发送请求,因为 # 符号后面的内容被浏览器认为是 URL 的一部分,而不是服务器请求的一部分。这意味着在使用 HashRouter 时,React 应用程序可以在客户端上运行,而无需服务器支持。

  1. BrowserRouter

BrowserRouter 使用 HTML5 的 history API 来实现路由。在 React 中,可以使用 <BrowserRouter> 组件来创建 BrowserRouter。例如:

jsxCopy codeimport { BrowserRouter, Route, Link } from 'react-router-dom';

function App() {
return (
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
);
}

在使用 BrowserRouter 时,URL 中的路径看起来像这样:http://example.com/about。BrowserRouter 通过 history API 在客户端和服务器之间发送请求,因此需要服务器支持。

  1. 区别

HashRouter 和 BrowserRouter 的主要区别在于它们如何处理 URL。HashRouter 使用 URL 中的 # 部分来实现路由,而 BrowserRouter 使用 HTML5 的 history API 来实现路由。HashRouter 不需要服务器支持,而 BrowserRouter 需要服务器支持。

  1. 原理

HashRouter 的原理是通过监听 window.location.hash 的变化来实现路由。当用户点击链接时,React Router 会根据链接的路径渲染相应的组件,并将路径添加到 URL 中的 # 部分。当用户点击浏览器的“后退”按钮时,React Router 会根据上一个 URL 中的 # 部分来渲染相应的组件。

BrowserRouter 的原理是通过 HTML5 的 history API 来实现路由。当用户点击链接时,React Router 会使用 history API 将路径添加到浏览器的历史记录中,并渲染相应的组件。当用户点击浏览器的“后退”

React Router 和浏览器原生 history API 在路由管理上主要有以下几个区别:

  1. 抽象级别:
  • React Router 提供了更高层次的抽象,如 <Router><Route>、和 <Link> 等组件,这些都是专门为了在 React 中更方便地管理路由而设计的。它处理了底层 history API 的很多细节,把操作抽象成了 React 组件和 hooks。
  • 原生 history API 更底层,直接作用于浏览器的历史记录栈。使用原生 history API 需要开发者自己编写更多的代码来管理 history 栈和渲染相应的组件。
  1. 便利性:
  • React Router 提供了声明式导航和编程式导航的选项,并且有大量的社区支持和文档,易于使用和学习。
  • 原生 history API 需要开发者自己处理 URL 与组件之间的关系映射,以及页面渲染的逻辑。
  1. 功能:
  • React Router 除了包含对原生 history API 的基本封装外,还提供了如路由守卫、路由懒加载、嵌套路由、重定向等高级功能。
  • 原生 history API 提供基本的历史记录管理功能,但是不包含上述 React Router 提供的高级应用路由需求。
  1. 集成:
  • React Router 是专为 React 设计的,与 React 的生命周期、状态管理等密切集成。
  • 原生 history API 与 React 没有直接关联,需要用户手动实现整合。
  1. 状态管理:
  • React Router 可以将路由状态管理与应用的状态管理(如使用 Redux)结合起来,使路由状态可预测和可管理。
  • 原生 history API 通常需要额外的状态管理逻辑来同步 UI 和 URL。
  1. 服务器渲染:
  • React Router 可以与服务器渲染一起使用,支持同构应用程序,即客户端和服务器都可以进行路由渲染。
  • 原生 history API 主要是针对客户端的,因此在服务器端渲染中需要额外的处理来模拟 routing 行为。

在考虑是否使用 React Router 或者原生 history API 时,通常需要考虑项目的复杂性、团队的熟悉度以及项目对路由的特定需求。对于大多数 React 项目而言,React Router 的便利性和其附加的高级特性通常使得它成为首选的路由解决方案。

表格对比

特性React Router原生 History API
抽象级别高层次抽象,提供了组件和 hooks底层 API,直接操作历史记录栈
便利性声明式和编程式导航,社区支持和文档齐全手动处理 URL 和组件映射,以及渲染逻辑
功能路由守卫、懒加载、嵌套路由、重定向等基本的历史记录管理
集成与 React 生命周期和状态管理紧密集成需要手动整合到 React 中
状态管理与应用的状态管理系统(如 Redux)可集成,路由状态可预测和可管理需要额外实现状态管理逻辑
服务器渲染支持同构应用程序,客户端和服务器都能渲染主要用于客户端,服务器端需要模拟
开发者工作量由库处理大部分的路由逻辑,简化开发者工作需要开发者手动编写代码管理路由
社区和资源广泛的社区和资源,易于获取帮助和解决方案相对较少的社区资源,通常需求独立解决
用户体验通常能提供更顺畅的用户体验可能因为实现不当导致的复杂性和用户体验问题

在 React 项目中,你完全可以不使用 react-router 而是使用浏览器原生的 history API 来手动管理路由。这通常会涉及以下几个步骤:

  1. 使用 history.pushState()history.replaceState() 方法来添加和修改浏览器历史条目。
  2. 侦听 popstate 事件来响应浏览器历史的变化。
  3. 根据当前的 URL 状态,手动渲染对应的 React 组件。

例如,下面是一个简单的例子,演示了如何在没有 react-router 的情况下使用原生 history API 来管理路由。

class App extends React.Component {
componentDidMount () {
// 当用户点击后退/前进按钮时触发路由变化
window.onpopstate = this.handlePopState
// 初始页面加载时处理路由
this.route()
}

handlePopState () {
// 处理路由变化
this.route()
}

route () {
const path = window.location.pathname
// 根据 path 渲染不同的组件
switch (path) {
case '/page1':
// 渲染 Page1 组件
break
case '/page2':
// 渲染 Page2 组件
break
// 其他路由分支...
default:
// 渲染默认组件或404页面
break
}
}

navigate (path) {
// 更新历史记录并触发路由变化
window.history.pushState(null, '', path)
this.route()
}

render () {
return (
<div>
<button onClick={() => this.navigate('/page1')}>Go to Page 1</button>
<button onClick={() => this.navigate('/page2')}>Go to Page 2</button>
{/* 这里根据路由渲染对应的组件 */}
</div>
)
}
}

// 实际的页面组件
const Page1 = () => <div>Page 1</div>
const Page2 = () => <div>Page 2</div>

尽管手动管理路由是可能的,但使用 react-router 这类专门设计的库通常会大大简化路由管理的工作。它为路径匹配、路由嵌套、重定向等提供了便利的抽象,并且和 React 的声明式方式很好地集成在一起。如果不是为了特别的原因,通常推荐使用现成的路由库来管理 React 应用的路由,以避免重新发明轮子。

React Router是React官方提供的用于构建单页应用的路由库,主要包括以下几个主要包和API:

主要包:

  1. react-router-dom:用于Web应用的路由库。
  2. react-router-native:用于原生应用(如React Native)的路由库。
  3. react-router-config:用于配置静态路由的工具包。

主要API:

  1. BrowserRouter:一个使用HTML5 history API实现的路由器组件,用于在Web应用中处理路由。
  2. HashRouter:一个使用URL hash值实现的路由器组件,用于在不支持HTML5 history API的Web应用中处理路由。
  3. Route:定义了路由匹配规则及对应的组件,可以在路由器中使用。
  4. Switch:用于渲染与当前URL匹配的第一个Route或Redirect,只能包含Route或Redirect组件。
  5. Link:用于创建导航链接,点击后会更新URL,触发路由的切换。
  6. NavLink:与Link类似,但在匹配当前URL时会添加指定的样式。

其他常用API:

  1. Redirect:用于重定向到指定的路由。
  2. withRouter:高阶组件,用于将路由器的相关信息(如history、location)传递给被包裹的组件。
  3. useHistory:自定义hook,用于在函数式组件中获取history对象。
  4. useLocation:自定义hook,用于在函数式组件中获取location对象。
  5. useParams:自定义hook,用于在函数式组件中获取路由参数。
  6. useRouteMatch:自定义hook,用于在函数式组件中获取与当前URL匹配的路由信息。

以上是React Router的主要包和API。根据具体的需求,你可以使用这些API来构建和处理路由相关的逻辑。

如何进行路由变化监听

在 React 中,你可以使用 React Router 库来进行路由变化的监听。React Router 是 React 的一个常用路由库,它提供了一组组件和 API 来帮助你在应用中管理路由。

下面是一个示例代码,演示如何使用 React Router 监听路由的变化:

然后,在你的 React 组件中,使用 BrowserRouter 或 HashRouter 组件包裹你的应用:

import React from 'react';
import { BrowserRouter, HashRouter } from 'react-router-dom';

function App() {
return (
// 使用 BrowserRouter 或 HashRouter 包裹你的应用
<BrowserRouter>
{/* 在这里编写你的应用内容 */}
</BrowserRouter>
);
}

export default App;

当使用函数组件时,可以使用 useEffect 钩子函数来监听路由变化。下面是修改后的示例代码:

import React, { useEffect } from 'react';
import { withRouter } from 'react-router-dom';

function MyComponent(props) {
useEffect(() => {
const handleRouteChange = (location, action) => {
// 路由发生变化时执行的处理逻辑
console.log('路由发生了变化', location, action);
};

// 在组件挂载后,添加路由变化的监听器
const unlisten = props.history.listen(handleRouteChange);

// 在组件卸载前,移除监听器
return () => {
unlisten();
};
}, [props.history]);

return (
<div>
{/* 在这里编写组件的内容 */}
</div>
);
}

// 使用 withRouter 高阶组件将路由信息传递给组件
export default withRouter(MyComponent);

在上面的代码中,我们使用了 useEffect 钩子函数来添加路由变化的监听器。在 useEffect 的回调函数中,我们定义了 handleRouteChange 方法来处理路由变化的逻辑。然后,通过 props.history.listen 方法来添加监听器,并将返回的取消监听函数赋值给 unlisten 变量。

同时,我们还在 useEffect 返回的清理函数中调用了 unlisten 函数,以确保在组件卸载时移除监听器。

需要注意的是,由于 useEffect 的依赖数组中包含了 props.history,所以每当 props.history 发生变化时(即路由发生变化时),useEffect 的回调函数会被调用,从而更新路由变化的监听器。

总结起来,通过使用 useEffect 钩子函数和 props.history.listen 方法,可以在函数组件中监听和响应路由的变化。

监听的核心原理基于useEffect,和useLocation,通过useEffect监听当前location的变化,这样就实现的最基本的监听结构:

const location = useLocation();
useEffect(() => {
//记录路径
}, [location]);

然后,我们可以在useEffect中记录和更新from、to的值,可以根据自己的需要选择from、to的数据类型,这里我使用了React-router提供的Location类型。

更新逻辑为:将to的值赋给from,然后将新的location赋值给to

import { Location, useLocation } from 'react-router-dom'

type LocationTrans = {
from: Location;
to: Location;
};

const location = useLocation()
const locationState = useRef<LocationTrans>({
from: null,
to: null
})

useEffect(() => {
locationState.current.from = locationState.current.to
locationState.current.to = location
}, [location])

最后,利用React的Context进行封装,将其封装成一个组件和一个hook,使用者可以通过这个组件来进行监听,通过hook快速访问数据。我将这些代码放在了同一个.tsx文件中,保证了逻辑的高内聚。

import React, { createContext, useContext, useEffect, useRef } from 'react'
import { Location, useLocation } from 'react-router-dom'

type LocationTrans = {
from: Location;
to: Location;
};

export const LocationContext =
createContext<React.MutableRefObject<LocationTrans>>(null)

export function WithLocationListener (props: { children: React.ReactNode }) {
const location = useLocation()

const locationState = useRef<LocationTrans>({
from: null,
to: null
})

useEffect(() => {
locationState.current.from = locationState.current.to
locationState.current.to = location
}, [location])

return (
<LocationContext.Provider value={locationState}>
{props.children}
</LocationContext.Provider>
)
}

export function useLocationConsumer (): LocationTrans {
const ref = useContext(LocationContext)
return ref.current
}

使用

这个组件只能在RouterProvider的子组件中使用,因为useLocation只能在这个范围内使用。

// import ....

function Layout () {
return (
<WithLocationListener>
{/* ..... */}
</WithLocationListener>
)
}

在需要用到路由信息的页面:

const { from, to } = useLocationConsumer()

参考文档

react-router 页面跳转时,是如何传递下一个页面参数的?

React Router 是一个用于管理前端路由的库,它与 React 应用程序集成在一起,提供了一种在单页面应用中处理路由的方式。React Router 并没有直接提供数据存储的功能,它主要负责路由的匹配和导航。

在 React Router 中,路由相关的数据主要存储在组件的 props 和组件的状态中。以下是一些常见的数据存储方式:

  1. 路由参数(Route Parameters): React Router 允许通过路径参数(如 /users/:id)传递参数给路由组件。这些参数可以通过 props.match.params 对象在路由组件中获取。路由参数通常用于标识唯一资源的ID或其他需要动态变化的数据。

  2. 查询参数(Query Parameters): 查询参数是通过 URL 查询字符串传递的键值对数据,如 /users?id=123&name=John。React Router 可以通过 props.location.search 属性获取查询字符串,并通过解析库(如 query-string)将其转换为 JavaScript 对象。查询参数通常用于筛选、分页或其他需要传递额外数据的场景。

  3. 路由状态(Route State): 在某些情况下,可能需要将一些状态信息传递给路由组件,例如从一个页面跳转到另一个页面时需要携带一些额外的状态。React Router 提供了 props.location.state 属性,可以用于存储和传递路由状态。

  4. 上下文(Context): React Router 提供了一个 Router 组件,可以使用 React 的上下文功能共享路由相关的数据。通过在 Router 组件的上下文中提供数据,可以在路由组件中访问该数据,而无需通过 props 层层传递。这在需要在多个嵌套层级中访问路由数据时非常方便。

总的来说,React Router 并没有专门的数据存储机制,它主要利用 React 组件的 props 和状态来传递和存储路由相关的数据。这些数据可以通过路由参数、查询参数、路由状态以及上下文等方式来传递和获取。根据具体的需求和场景,可以选择适合的方式来存储和管理路由相关的数据。

路由状态是如何存储的

在 React Router 中,路由状态可以通过 props.location.state 属性来存储和获取。

当使用 React Router 进行页面导航时,可以通过 history.pushhistory.replace 方法传递一个包含状态数据的对象作为第二个参数。例如:

history.push('/dashboard', { isLoggedIn: true, username: 'John' });

这个对象会被存储在新页面的 props.location.state 中,可以在目标页面的组件中通过 props.location.state 来访问它。例如:

import { useLocation } from 'react-router-dom';

function Dashboard() {
const location = useLocation();
const { isLoggedIn, username } = location.state;

// 使用路由状态数据
// ...
}

需要注意的是,路由状态仅在通过 history.pushhistory.replace 导航到新页面时才可用。如果用户通过浏览器的前进/后退按钮进行导航,或者直接输入 URL 地址访问页面,路由状态将不会被保留。

另外,路由状态也可以在类组件中通过 this.props.location.state 进行访问,或者在函数组件中使用 props.location.state

props.location.state 数据是如何存储的

在 React Router 中,路由状态数据实际上是存储在客户端的内存中。

当使用 history.pushhistory.replace 方法导航到一个新页面时,React Router 将路由状态数据作为对象附加到浏览器历史记录中的对应路由条目。这个对象会存储在浏览器的会话历史中,并在新页面加载时被 React Router 读取并提供给组件。

具体地说,React Router 使用 HTML5 的 History API(pushStatereplaceState 方法)来实现路由导航,并将路由状态数据作为一个特殊的字段存储在历史记录中。这个字段通常被称为 state 字段,用于存储路由状态数据。

在浏览器中,历史记录和相应的状态数据会被保存在内存中。当用户进行前进、后退或直接访问某个 URL 时,浏览器会根据历史记录加载对应的页面,并将相关的状态数据提供给 React Router。这样,组件就能够通过 props.location.state 来访问之前存储的路由状态数据。

需要注意的是,路由状态数据仅在客户端内存中存在,每个用户的路由状态是独立的。如果用户刷新页面或关闭浏览器,路由状态数据将丢失,并需要重新通过导航操作来设置。因此,路由状态适合存储短期或临时的数据,而对于长期或持久化的数据,应该考虑其他的数据存储方式,如服务器端存储或状态管理库。

如何实现路由守卫

在 React 中,虽然没有内置的路由守卫(Route Guards)功能,但可以使用第三方库来实现类似的功能。最常用的第三方路由库是 React Router。

React Router 提供了一些组件和钩子函数,可以用于在路由导航过程中进行拦截和控制。

  1. <Route> 组件:可以在路由配置中定义特定路由的守卫逻辑。例如,可以设置 render 属性或者 component 属性来渲染组件,并在渲染前进行守卫逻辑的判断。

  2. useHistory 钩子:可以获取当前路由的历史记录,并通过 history 对象进行路由导航的控制。可以使用 history 对象的 pushreplace 方法来切换路由,并在切换前进行守卫逻辑的判断。

  3. useLocation 钩子:可以获取当前的路由位置信息,包括路径、查询参数等。可以根据这些信息进行守卫逻辑的判断。

下面是一个使用 React Router 实现路由守卫的示例:

import { BrowserRouter as Router, Route, useHistory } from 'react-router-dom'

function App () {
const history = useHistory()

const isAuthenticated = () => {
// 判断用户是否已登录
return true
}

const requireAuth = (Component) => {
return () => {
if (isAuthenticated()) {
return <Component />
} else {
history.push('/login')
return null
}
}
}

return (
<Router>
<Route path="/home" render={requireAuth(Home)} />
<Route path="/login" component={Login} />
<Route path="/dashboard" render={requireAuth(Dashboard)} />
</Router>
)
}

在上述示例中,requireAuth 是一个自定义的函数,用于判断是否需要进行权限校验。在 render 属性中,我们调用 requireAuth 函数包裹组件,根据用户是否已登录来判断是否渲染该组件。如果用户未登录,则使用 history.push 方法进行路由跳转到登录页面。

通过使用 React Router 提供的组件和钩子函数,我们可以实现类似于路由守卫的功能,进行路由的拦截和控制。

参考文档

22%