状态管理
router
React Router 是一个流行的第三方库,它允许在 React 应用程序中实现路由功能。React Router 支持两种路由方式:HashRouter 和 BrowserRouter。
- 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 应用程序可以在客户端上运行,而无需服务器支持。
- 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 在客户端和服务器之间发送请求,因此需要服务器支持。
- 区别
HashRouter 和 BrowserRouter 的主要区别在于它们如何处理 URL。HashRouter 使用 URL 中的 # 部分来实现路由,而 BrowserRouter 使用 HTML5 的 history API 来实现路由。HashRouter 不需要服务器支持,而 BrowserRouter 需要服务器支持。
- 原理
HashRouter 的原理是通过监听 window.location.hash
的变化来实现路由。当用户点击链接时,React Router 会根据链接的路径渲染相应的组件,并将路径添加到 URL 中的 # 部分。当用户点击浏览器的“后退”按钮时,React Router 会根据上一个 URL 中的 # 部分来渲染相应的组件。
BrowserRouter 的原理是通过 HTML5 的 history API 来实现路由。当用户点击链接时,React Router 会使用 history API 将路径添加到浏览器的历史记录中,并渲染相应的组件。当用户点击浏览器的“后退”
React Router 和浏览器原生 history API 在路由管理上主要有以下几个区别:
- 抽象级别:
- React Router 提供了更高层次的抽象,如
<Router>
、<Route>
、和<Link>
等组件,这些都是专门为了在 React 中更方便地管理路由而设计的。它处理了底层 history API 的很多细节,把操作抽象成了 React 组件和 hooks。 - 原生 history API 更底层,直接作用于浏览器的历史记录栈。使用原生 history API 需要开发者自己编写更多的代码来管理 history 栈和渲染相应的组件。
- 便利性:
- React Router 提供了声明式导航和编程式导航的选项,并且有大量的社区支持和文档,易于使用和学习。
- 原生 history API 需要开发者自己处理 URL 与组件之间的关系映射,以及页面渲染的逻辑。
- 功能:
- React Router 除了包含对原生 history API 的基本封装外,还提供了如路由守卫、路由懒加载、嵌套路由、重定向等高级功能。
- 原生 history API 提供基本的历史记录管理功能,但是不包含上述 React Router 提供的高级应用路由需求。
- 集成:
- React Router 是专为 React 设计的,与 React 的生命周期、状态管理等密切集成。
- 原生 history API 与 React 没有直接关联,需要用户手动实现整合。
- 状态管理:
- React Router 可以将路由状态管理与应用的状态管理(如使用 Redux)结合起来,使路由状态可预测和可管理。
- 原生 history API 通常需要额外的状态管理逻辑来同步 UI 和 URL。
- 服务器渲染:
- 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 来手动管理路由。这通常会涉及以下几个步骤:
- 使用
history.pushState()
和history.replaceState()
方法来添加和修改浏览器历史条目。 - 侦听
popstate
事件来响应浏览器历史的变化。 - 根据当前的 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:
主要包:
- react-router-dom:用于Web应用的路由库。
- react-router-native:用于原生应用(如React Native)的路由库。
- react-router-config:用于配置静态路由的工具包。
主要API:
- BrowserRouter:一个使用HTML5 history API实现的路由器组件,用于在Web应用中处理路由。
- HashRouter:一个使用URL hash值实现的路由器组件,用于在不支持HTML5 history API的Web应用中处理路由。
- Route:定义了路由匹配规则及对应的组件,可以在路由器中使用。
- Switch:用于渲染与当前URL匹配的第一个Route或Redirect,只能包含Route或Redirect组件。
- Link:用于创建导航链接,点击后会更新URL,触发路由的切换。
- NavLink:与Link类似,但在匹配当前URL时会添加指定的样式。
其他常用API:
- Redirect:用于重定向到指定的路由。
- withRouter:高阶组件,用于将路由器的相关信息(如history、location)传递给被包裹的组件。
- useHistory:自定义hook,用于在函数式组件中获取history对象。
- useLocation:自定义hook,用于在函数式组件中获取location对象。
- useParams:自定义hook,用于在函数式组件中获取路由参数。
- 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 和组件的状态中。以下是一些常见的数据存储方式:
-
路由参数(Route Parameters): React Router 允许通过路径参数(如
/users/:id
)传递参数给路由组件。这些参数可以通过props.match.params
对象在路由组件中获取。路由参数通常用于标识唯一资源的ID或其他需要动态变化的数据。 -
查询参数(Query Parameters): 查询参数是通过 URL 查询字符串传递的键值对数据,如
/users?id=123&name=John
。React Router 可以通过props.location.search
属性获取查询字符串,并通过解析库(如query-string
)将其转换为 JavaScript 对象。查询参数通常用于筛选、分页或其他需要传递额外数据的场景。 -
路由状态(Route State): 在某些情况下,可能需要将一些状态信息传递给路由组件,例如从一个页面跳转到另一个页面时需要携带一些额外的状态。React Router 提供了
props.location.state
属性,可以用于存储和传递路由状态。 -
上下文(Context): React Router 提供了一个
Router
组件,可以使用 React 的上下文功能共享路由相关的数据。通过在Router
组件的上下文中提供数据,可以在路由组件中访问该数据,而无需通过 props 层层传递。这在需要在多个嵌套层级中访问路由数据时非常方便。
总的来说,React Router 并没有专门的数据存储机制,它主要利用 React 组件的 props 和状态来传递和存储路由相关的数据。这些数据可以通过路由参数、查询参数、路由状态以及上下文等方式来传递和获取。根据具体的需求和场景,可以选择适合的方式来存储和管理路由相关的数据。
路由状态是如何存储的
在 React Router 中,路由状态可以通过 props.location.state
属性来存储和获取。
当使用 React Router 进行页面导航时,可以通过 history.push
或 history.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.push
或 history.replace
导航到新页面时才可用。如果用户通过浏览器的前进/后退按钮进行导航,或者直接输入 URL 地址访问页面,路由状态将不会被保留。
另外,路由状态也可以在类组件中通过 this.props.location.state
进行访问,或者在函数组件中使用 props.location.state
。
props.location.state 数据是如何存储的
在 React Router 中,路由状态数据实际上是存储在客户端的内存中。
当使用 history.push
或 history.replace
方法导航到一个新页面时,React Router 将路由状态数据作为对象附加到浏览器历史记录中的对应路由条目。这个对象会存储在浏览器的会话历史中,并在新页面加载时被 React Router 读取并提供给组件。
具体地说,React Router 使用 HTML5 的 History API(pushState
或 replaceState
方法)来实现路由导航,并将路由状态数据作为一个特殊的字段存储在历史记录中。这个字段通常被称为 state
字段,用于存储路由状态数据。
在浏览器中,历史记录和相应的状态数据会被保存在内存中。当用户进行前进、后退或直接访问某个 URL 时,浏览器会根据历史记录加载对应的页面,并将相关的状态数据提供给 React Router。这样,组件就能够通过 props.location.state
来访问之前存储的路由状态数据。
需要注意的是,路由状态数据仅在客户端内存中存在,每个用户的路由状态是独立的。如果用户刷新页面或关闭浏览器,路由状态数据将丢失,并需要重新通过导航操作来设置。因此,路由状态适合存储短期或临时的数据,而对于长期或持久化的数据,应该考虑其他的数据存储方式,如服务器端存储或状态管理库。
如何实现路由守卫
在 React 中,虽然没有内置的路由守卫(Route Guards)功能,但可以使用第三方库来实现类似的功能。最常用的第三方路由库是 React Router。
React Router 提供了一些组件和钩子函数,可以用于在路由导航过程中进行拦截和控制。
-
<Route>
组件:可以在路由配置中定义特定路由的守卫逻辑。例如,可以设置render
属性或者component
属性来渲染组件,并在渲染前进行守卫逻辑的判断。 -
useHistory
钩子:可以获取当前路由的历史记录,并通过history
对象进行路由导航的控制。可以使用history
对象的push
、replace
方法来切换路由,并在切换前进行守卫逻辑的判断。 -
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 提供的组件和钩子函数,我们可以实现类似于路由守卫的功能,进行路由的拦截和控制。
参考文档