接着介绍React中的高阶组件,上篇文章React中的高阶组件(三)
高阶组件(Higher-Order Component, HOC) 是React中为了复用组件的函数。一个 HOC 是一个函数,接收一个组件并返回一个增强版的组件。通过 HOC,开发者可以将通用逻辑提取到可复用的组件包装器中,避免代码重复。
13. withAuth(自定义 HOC)
- 功能:检查用户是否已登录,并根据情况跳转或展示不同内容。
- 示例:
这段代码定义了一个高阶组件 withAuth,用于保护 React 应用中的某些组件,使它们只能在用户登录的情况下被渲染。该高阶组件会检查用户的登录状态,如果用户未登录,则会将用户重定向到登录页面。
const withAuth = (WrappedComponent) => {
return class extends React.Component {
componentDidMount() {
// 检查用户是否登录,如果未登录则重定向到登录页面
if (!Auth.isLoggedIn()) {
this.props.history.push("/login");
}
}
render() {
// 如果用户登录,则渲染传入的 WrappedComponent,否则返回 null(不渲染组件)
return Auth.isLoggedIn() ? <WrappedComponent {...this.props} /> : null;
}
};
};
export default withAuth;
代码解析
1. withAuth 是一个高阶组件
- 高阶组件(HOC) 是一个函数,它接受一个组件(
WrappedComponent)作为参数,并返回一个新的组件。 withAuth的作用是增强传入的组件,使其具备检查用户登录状态的功能。
2. componentDidMount
componentDidMount是 React 组件的一个生命周期方法。当组件首次渲染到页面上后会被调用。- 在这个方法中,
withAuth会检查用户是否已经登录,调用Auth.isLoggedIn()方法来进行验证。 - 如果用户未登录,使用
this.props.history.push("/login")将用户重定向到/login路由,通常这是登录页面。
3. render 方法
render方法决定组件的渲染逻辑。withAuth高阶组件会检查Auth.isLoggedIn()返回的值。如果用户已登录(返回true),则渲染传入的WrappedComponent,并将当前组件的props传递给它。- 如果用户未登录(返回
false),则返回null,即不会渲染任何内容。
4. Auth.isLoggedIn()
Auth.isLoggedIn()是假定的一个认证方法,用来判断用户是否已经登录。具体实现可以是检查本地存储、验证 token,或调用服务器 API 等等。
5. this.props.history.push("/login")
- 该代码使用
react-router-dom提供的history对象来进行路由跳转。this.props.history.push("/login")会将用户重定向到/login页面。 this.props.history通常是通过withRouter或Route组件传递的,如果当前组件没有history,需要确保WrappedComponent是通过路由系统包裹的。
应用示例
假设我们有一个需要保护的页面组件 Dashboard,只有在用户登录后才能访问该页面。
import React from "react";
import withAuth from "./withAuth"; // 引入我们定义的 withAuth HOC
class Dashboard extends React.Component {
render() {
return <h1>Welcome to the Dashboard</h1>;
}
}
export default withAuth(Dashboard); // 使用 withAuth 包装 Dashboard 组件
在这个例子中:
Dashboard是一个需要登录才能访问的页面组件。- 通过
withAuth包装Dashboard组件,确保用户只有在登录状态下才能看到该页面。如果用户未登录,会自动被重定向到/login页面。
总结
withAuth高阶组件是一种保护页面或组件的方式,确保用户在登录后才能访问某些内容。- 使用
componentDidMount检查用户的登录状态,如果未登录,则重定向到登录页面。 - 通过
Auth.isLoggedIn()来验证用户是否已登录,且使用this.props.history.push()进行路由跳转。
14. withPermissions(权限控制)
- 功能:检查用户权限,并决定是否展示组件。
- 示例:这段代码定义了一个高阶组件
withPermissions,用于根据用户权限控制组件的访问。如果用户没有所需的权限,组件将显示“Access Denied”(拒绝访问)的消息;如果用户拥有所需的权限,则渲染传入的组件。
const withPermissions = (WrappedComponent, requiredPermission) => {
return class extends React.Component {
render() {
// 检查用户是否拥有所需的权限
if (!this.props.permissions.includes(requiredPermission)) {
// 如果用户没有权限,返回“Access Denied”信息
return <div>Access Denied</div>;
}
// 如果用户有权限,渲染传入的组件并传递当前的props
return <WrappedComponent {...this.props} />;
}
};
};
export default withPermissions;
1. withPermissions 是一个高阶组件(HOC)
-
高阶组件(HOC) 是一个接受组件作为参数并返回新组件的函数。
withPermissions这个 HOC 用于增强传入的组件,使其具备权限检查的功能。 -
它接受两个参数:
WrappedComponent:被包装的组件(即需要权限控制的组件)。requiredPermission:访问该组件所需的权限。
2. this.props.permissions
-
该代码假设
this.props.permissions是一个包含用户当前权限的数组。它会通过includes()方法检查用户是否具有所需的权限。this.props.permissions.includes(requiredPermission):这行代码用于判断用户的权限是否包含在传入的requiredPermission中。如果包含,则表示用户有权限;否则表示没有权限。
3. render 方法
- 权限检查:在
render方法中,首先检查用户是否拥有requiredPermission。如果用户没有权限,render方法会返回一个<div>元素,显示“Access Denied”消息,表示用户无权访问该组件。 - 权限通过:如果用户拥有权限,
render方法将渲染WrappedComponent,并将所有的props传递给它(通过{...this.props})。
4. 组件返回
withPermissions返回的是一个新的组件,内部封装了权限逻辑,并根据用户的权限条件决定是否渲染WrappedComponent。
应用场景
假设我们有一个组件 AdminPanel,只有用户具有 admin 权限时才能访问。通过使用 withPermissions,我们可以限制用户访问该组件。
import React from "react";
import withPermissions from "./withPermissions"; // 引入 withPermissions HOC
const AdminPanel = () => {
return <div>Welcome to Admin Panel</div>;
};
// 使用 withPermissions 包装 AdminPanel 组件,确保只有拥有 "admin" 权限的用户可以访问
export default withPermissions(AdminPanel, "admin");
用法说明
AdminPanel是一个普通的 React 组件。通过withPermissions高阶组件包装后,只有拥有admin权限的用户才能看到AdminPanel的内容。withPermissions在render方法中进行权限检查。如果用户没有权限,将显示“Access Denied”;如果用户有权限,则显示AdminPanel的内容。
应用示例
import React from "react";
import AdminPanel from "./AdminPanel"; // 假设 AdminPanel 是被 withPermissions 包装的组件
const App = () => {
const userPermissions = ["user", "admin"]; // 假设用户拥有的权限
return (
<div>
{/* 传递用户权限给 AdminPanel */}
<AdminPanel permissions={userPermissions} />
</div>
);
};
export default App;
在这个示例中:
- 用户权限是
["user", "admin"],表示该用户拥有普通用户和管理员权限。 - 当
AdminPanel被渲染时,withPermissions会检查userPermissions中是否包含admin权限,如果有,则允许访问;否则显示“Access Denied”。
总结
withPermissions高阶组件用于权限控制,保护组件的访问权限。- 通过检查
this.props.permissions数组中的权限,决定是否渲染包装的组件。 - 增强功能:
withPermissions为传入的组件添加权限逻辑,允许更好地管理用户访问权限,尤其是在需要根据角色或权限区分访问内容的场景中非常有用。
15. withMemo(性能优化)
- 功能:使用
React.memo来避免不必要的重新渲染。 - 示例:这段代码使用了 React 的
React.memo函数来优化组件性能。React.memo是一个高阶组件(HOC),它用于包裹一个函数组件,只有在组件的props发生变化时,组件才会重新渲染。
const MyComponent = React.memo(({ name }) => {
return <div>{name}</div>;
});
1. React.memo
React.memo是一个优化工具。它接收一个组件,并返回一个“记忆化”的版本。记忆化版本会对组件的props进行浅比较,只有当props发生变化时,才会触发重新渲染。- 这对于性能优化非常有用,特别是在大型应用中,避免不必要的渲染,提升性能。
2. 组件 MyComponent
- 这是一个非常简单的函数组件,接收一个
name作为props,并在div中显示该name。 - 该组件被
React.memo包裹,因此如果name的值在更新时保持不变,组件不会重新渲染。
3. React.memo 的工作机制
- 浅比较:
React.memo默认对传入的props进行浅层比较。如果props没有改变,React 会跳过重新渲染组件。 - 在这个例子中,
name是一个简单的字符串,因此比较的效率非常高。如果name的值保持不变,则不会触发重新渲染。
4. 应用场景
使用 React.memo 的典型场景是当父组件重新渲染时,子组件不需要重新渲染。特别是在父组件频繁更新的情况下,通过 React.memo 可以避免不必要的子组件更新,从而提升应用的性能。
示例:父组件和子组件
import React, { useState } from "react";
const MyComponent = React.memo(({ name }) => {
console.log("MyComponent rendered");
return <div>{name}</div>;
});
const ParentComponent = () => {
//`useState` 是 React 中用来在函数组件中引入状态的 Hook。
const [count, setCount] = useState(0);
const [name, setName] = useState("John");
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<MyComponent name={name} />
</div>
);
};
export default ParentComponent;
说明:
ParentComponent包含两个state,一个是count,一个是name。count的变化不会影响name。MyComponent被React.memo包裹,因此当count变化时,由于name没有改变,MyComponent不会重新渲染。- 控制台输出:当你点击
Increment Count按钮时,你会发现MyComponent没有重新渲染,因为它的name没有变化,避免了不必要的重新渲染。
总结
React.memo是一种用于性能优化的工具,特别是在组件不需要频繁重新渲染的情况下非常有用。- 在高频渲染场景中,通过
React.memo包裹子组件,可以避免不必要的渲染,提升应用性能。 - 它只会在
props发生变化时触发组件的重新渲染,适用于那些props变化频率较低的组件。
16. withPagination 处理分页数据逻辑的高阶组件
-
功能:
withPagination是用于处理分页数据的 HOC。它可以帮助组件更方便地处理数据的分页加载、页码切换等逻辑。 -
示例:
withPagination是一个处理分页逻辑的高阶组件,用来增强某个组件的分页数据处理能力。分页是指将大量数据按页划分,让用户可以逐页查看,而不是一次性加载所有数据。这个高阶组件封装了分页相关的状态管理和数据获取逻辑,使得被包裹的组件专注于渲染数据。
//`withPagination` 是一个高阶组件(HOC),它接受两个参数:
//`WrappedComponent`:被包装的组件,这里是 `AdminComponent`,用来展示分页数据。
//`fetchData`:用于获取数据的函数。每当页面切换时,`fetchData` 会被调用以获取新页的数据。
const withPagination = (WrappedComponent, fetchData) => {
//这个 HOC 返回一个新的类组件,这个类组件拥有自己独立的状态管理和生命周期。
return class extends React.Component {
state = {
data: [], //当前页面的数据
currentPage: 1, //当前显示的页码
totalPages: 1, //总页数,通常由 API 响应提供
};
//生命周期方法
//当组件挂载时,`componentDidMount` 生命周期方法会自动调用 `loadData` 方法加载当前页的数据。
//初始情况下,`currentPage` 的值为 `1`。
componentDidMount() {
this.loadData(this.state.currentPage);
}
//`loadData` 是一个异步方法,它根据传入的 `page` 参数调用 `fetchData` 获取数据,
//并将结果保存到组件的状态中。`fetchData` 函数通常是一个 API 调用,返回分页数据和总页数。
//然后使用 `this.setState` 更新组件的状态,包括 `data`、`totalPages` 和 `currentPage`。
loadData = async (page) => {
const response = await fetchData(page);
this.setState({
data: response.data,
totalPages: response.totalPages,
currentPage: page,
});
};
//`handlePageChange` 用于切换页面。当用户点击“下一页”或“上一页”按钮时,传递不同的 `page` 参数来调用 `loadData`,从而加载新页的数据。
handlePageChange = (page) => {
this.loadData(page);
};
//`render` 方法负责渲染包装的组件 `WrappedComponent`,并将分页相关的 `data`、
//`currentPage`、`totalPages` 和 `onPageChange` 传递给它。
//`...this.props` 将父组件传递的其他属性也一并传给 `WrappedComponent`。
render() {
return (
<WrappedComponent
data={this.state.data}
currentPage={this.state.currentPage}
totalPages={this.state.totalPages}
onPageChange={this.handlePageChange}
{...this.props}
/>
);
}
};
};
// 应用组件`AdminComponent`
`AdminComponent` 是一个展示组件,用于显示分页数据。它接收以下属性:
- `data`:当前页的数据,用于展示。
- `currentPage`:当前页码。
- `totalPages`:总页数。
- `onPageChange`:当用户点击“下一页”或“上一页”按钮时调用该函数。
该组件根据 `data` 显示列表,并渲染上一页和下一页按钮,分别用于改变 `currentPage`。
///////////////////////////////////////////////////////////////////////////
const AdminComponent = ({ data, currentPage, totalPages, onPageChange }) => (
<div>
<h2>Admin Page</h2>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div>
<button disabled={currentPage === 1} onClick={() => onPageChange(currentPage - 1)}>
Previous
</button>
<span>{currentPage} / {totalPages}</span>
<button disabled={currentPage === totalPages} onClick={() => onPageChange(currentPage + 1)}>
Next
</button>
</div>
</div>
);
//`fetchData` 函数
//`fetchData` 是一个模拟的异步函数,返回与当前页面相关的数据。在实际应用中,这将是一个 API 调用,
//返回服务器响应的分页数据和总页数。
const fetchData = async (page) => {
// Simulate an API call
return {
data: [{ id: 1, name: "Page Data " + page }],
totalPages: 5,
};
};
//通过 `withPagination` HOC,`AdminComponent` 获得了分页逻辑。调用 `withPagination` 时,
//将 `fetchData` 传入,用于获取分页数据。最终,返回的组件会自动处理数据加载和页码切换。
export default withPagination(AdminComponent, fetchData);
总结:
withPagination是一个高阶组件,它将分页逻辑封装起来,通过状态管理和生命周期方法处理数据加载。AdminComponent负责展示分页数据,而withPagination处理了复杂的分页逻辑,如数据加载、页码切换等。withPagination通过接受fetchData函数,使其适用于任何组件和数据源,极大增强了组件的复用性。
17. withAnalytics(为组件添加用户行为跟踪的高阶组件)
- 功能:
withAnalytics是用于跟踪用户行为的 HOC,通常用于分析用户的点击、浏览等行为,将这些数据发送给 Google Analytics 或其他分析工具。 - 示例:这段代码实现了一个高阶组件(HOC)
withAnalytics,用于为 React 组件添加用户行为跟踪功能(例如页面加载事件跟踪)。通过封装componentDidMount生命周期方法,withAnalyticsHOC 可以在组件挂载时自动触发事件跟踪。
const withAnalytics = (WrappedComponent, eventName) => {
return class extends React.Component {
componentDidMount() {
this.trackEvent(eventName);
}
trackEvent = (event) => {
console.log(`Tracking event: ${event}`);
// In real cases, send to analytics services like Google Analytics
// window.gtag('event', event);
};
render() {
return <WrappedComponent {...this.props} />;
}
};
};
主要功能:
-
withAnalytics是一个接受两个参数的高阶组件:WrappedComponent:被包装的组件。这里是一个将要应用分析跟踪功能的 React 组件。eventName:指定跟踪的事件名称,当组件挂载时(通过componentDidMount)触发。
-
componentDidMount生命周期方法:- 当包装的组件被挂载时,
componentDidMount会被调用。 - 在这里,
trackEvent方法会被调用,并传入eventName进行事件跟踪。
- 当包装的组件被挂载时,
-
trackEvent方法:trackEvent负责处理实际的事件跟踪操作。在此示例中,它简单地输出跟踪事件到控制台(console.log)。- 实际应用中,可能会调用诸如 Google Analytics 等分析服务来记录事件。注释中的
window.gtag('event', event)是发送数据到 Google Analytics 的示例。
-
render方法:- 该方法将
WrappedComponent渲染,并通过{...this.props}将所有的原始属性传递给包装的组件。这确保了WrappedComponent可以正常接受和使用从父组件传递来的属性。
- 该方法将
withAnalytics 的使用:
const Navbar = ({ t }) => (
<nav>
<a href="/home">{t('navbar.home')}</a>
<a href="/about">{t('navbar.about')}</a>
</nav>
);
export default withAnalytics(Navbar, 'Navbar Loaded');
-
Navbar是一个简单的导航栏组件,使用t函数(通常用于国际化)来显示导航链接的文本。 -
t('navbar.home')和t('navbar.about')分别为导航链接显示对应的文本(如“Home”和“About”),通常通过国际化库(如i18next)动态加载。
通过 withAnalytics 高阶组件包装 Navbar,并传入事件名称 'Navbar Loaded',这个组件在加载时会触发跟踪事件。具体来说:
- 当
Navbar组件被挂载时,withAnalytics组件的componentDidMount被调用,trackEvent('Navbar Loaded')触发,控制台输出"Tracking event: Navbar Loaded"。 - 实际生产环境中,可以将这类事件发送给诸如 Google Analytics、Mixpanel 等用户行为分析平台。
HOC 如何工作
- 组件挂载:当
Navbar被嵌入页面时,React 会执行withAnalytics包装类中的componentDidMount方法。 - 触发跟踪事件:此时
trackEvent被调用,输出"Tracking event: Navbar Loaded",模拟了事件跟踪。 - 渲染原组件:
withAnalytics的render方法渲染Navbar组件,并将所有原始属性传递给它,使其正常工作。
实际应用场景:
- 分析页面访问量:通过记录页面或组件加载事件,可以跟踪用户访问行为,了解哪些页面或功能使用得最多。
- 跟踪用户点击:可以扩展
withAnalytics,不仅在组件加载时,还可以在用户与组件交互时触发跟踪,例如按钮点击或表单提交。 - 性能监控:还可以通过这种方式监控组件的性能,记录加载时间等。
扩展代码示例:跟踪用户点击
const withAnalytics = (WrappedComponent, eventName) => {
return class extends React.Component {
componentDidMount() {
this.trackEvent(eventName);
}
trackEvent = (event) => {
console.log(`Tracking event: ${event}`);
// Analytics service like Google Analytics can be integrated here
};
handleClick = () => {
this.trackEvent('User clicked the component');
};
render() {
return (
<div onClick={this.handleClick}>
<WrappedComponent {...this.props} />
</div>
);
}
};
};
在这个扩展版本中,withAnalytics 不仅在组件挂载时触发跟踪事件,还在用户点击组件时调用 handleClick,从而记录用户点击事件。
功能详解:
-
componentDidMount():- 当
WrappedComponent被挂载时,withAnalytics的componentDidMount会调用trackEvent函数,记录一个与组件加载相关的事件。 eventName参数用于指定要跟踪的事件名称。
- 当
-
trackEvent(event):- 该函数接收一个事件名称,并在控制台输出跟踪的事件。实际项目中可以将该事件发送到第三方分析服务,如 Google Analytics。
- 示例中,它只是简单地打印跟踪信息
Tracking event: {event}。
-
handleClick():- 当用户点击封装组件时,
handleClick会被触发,调用trackEvent('User clicked the component'),记录用户点击事件。
- 当用户点击封装组件时,
-
render():render方法将WrappedComponent包裹在一个div内,并将所有props传递给WrappedComponent。- 当
div被点击时,handleClick会触发,从而记录用户点击行为。
如何使用 withAnalytics:
假设我们有一个简单的按钮组件 ButtonComponent,我们希望在用户点击按钮时跟踪点击事件,并记录按钮加载时的事件。
1. 创建一个简单的组件 ButtonComponent:
const ButtonComponent = ({ label }) => {
return <button>{label}</button>;
};
- 使用
withAnalytics高阶组件包裹ButtonComponent:
const TrackedButton = withAnalytics(ButtonComponent, 'Button Loaded');
// 在应用中使用 TrackedButton 组件
const App = () => (
<div>
<h1>Welcome to Analytics Example</h1>
<TrackedButton label="Click Me" />
</div>
);
export default App;
运行效果:
- 页面加载时:当
TrackedButton组件被挂载时,withAnalytics会记录事件Button Loaded,输出Tracking event: Button Loaded。 - 点击按钮时:当用户点击按钮时,
handleClick会被触发,记录点击事件User clicked the component,输出Tracking event: User clicked the component。
18. withErrorHandler(捕获异步错误并显示用户友好提示)
- 功能:
withErrorHandler是用于捕获组件中的异步错误的 HOC。它可以帮助处理如 API 调用失败等错误,防止程序崩溃,并向用户展示友好的错误提示。 - 示例:这段代码实现了一个高阶组件(HOC)
withErrorHandler,用于捕获 React 组件中的错误,并处理这些错误。它主要是基于 React 的componentDidCatch生命周期方法,提供一种优雅的方式处理组件中的运行时错误。
定义高阶组件 withErrorHandler
const withErrorHandler = (WrappedComponent, errorHandler) => {
return class extends React.Component {
state = {
hasError: false,
};
componentDidCatch(error, info) {
this.setState({ hasError: true });
errorHandler(error, info);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong. Please try again later.</div>;
}
return <WrappedComponent {...this.props} />;
}
};
};
-
状态管理:
state具有一个布尔值hasError,用于表示是否发生了错误。如果发生错误,则该状态被设置为true。
-
componentDidCatch(error, info):- 这是 React 的错误边界生命周期方法。它可以捕获子组件树中的错误,并且不会影响应用程序的整体崩溃。
componentDidCatch接收两个参数:error(错误对象)和info(包含更多错误信息的对象,通常与组件的堆栈相关)。- 捕获错误后,通过调用
errorHandler函数,来记录或处理这些错误。
-
render():- 当
hasError状态为true时,显示错误消息,提示用户 "Something went wrong. Please try again later."。 - 否则,正常渲染
WrappedComponent,并将所有传递给 HOC 的props继续传递给该组件。
- 当
使用示例
// `MyComponent` 是一个普通的 React 组件,在 `componentDidMount` 生命周期中故意抛出一个错误(`Test error`)。
class MyComponent extends React.Component {
componentDidMount() {
throw new Error('Test error');
}
render() {
return <div>My Component Content</div>;
}
}
//错误处理函数 `logError`
//`logError` 函数负责处理错误,通常可以将错误记录到控制台,也可以将错误发送到外部日志服务
//(如 Sentry、Datadog 或自定义的日志记录系统)。
const logError = (error, info) => {
console.error("Error logged: ", error, info);
// Send the error to an external logging service
};
//**将高阶组件应用到 `MyComponent`**
export default withErrorHandler(MyComponent, logError);
在这个示例中,MyComponent 抛出错误后,withErrorHandler 捕获并处理错误,并通过 logError 函数记录该错误,显示友好的用户提示。
通过 withErrorHandler 包装 MyComponent,现在如果 MyComponent 在渲染或生命周期中抛出错误,withErrorHandler 会捕获该错误并调用 logError 处理错误,同时向用户显示友好的错误消息。
实际应用场景
-
错误边界:
- 在大型 React 应用中,可以用
withErrorHandler高阶组件为关键组件添加错误边界,防止个别组件的错误导致整个应用崩溃。
- 在大型 React 应用中,可以用
-
日志服务集成:
errorHandler可以与第三方服务(如 Sentry、LogRocket、NewRelic)集成,将捕获到的错误信息发送到外部日志或监控平台,从而帮助开发者更好地追踪和修复错误。
-
用户体验:
- 当错误发生时,用户会看到一个友好的错误提示,而不是白屏或应用崩溃,从而提升用户体验。
其他 6 种高阶组件(简述)
-
withBreakpoint。 作用:根据窗口大小或设备类型动态调整组件布局,通常结合 Material-UI 的 theme.breakpoints 实现响应式设计。
-
withFeatureToggle 作用:根据功能开关动态控制组件的显示与隐藏,常用于渐进式发布或 A/B 测试。
-
withSSR 作用:处理服务端渲染逻辑,支持在服务端预加载数据后再进行组件渲染,通常与 Next.js 等框架集成。
-
withAnimation 作用:通过 HOC 为组件添加动画效果,通常与 react-spring 或 framer-motion 等动画库结合使用。
-
withApollo 作用:为组件添加 Apollo 客户端功能,支持与 GraphQL API 交互,通常用于数据请求和状态管理。
-
withSEO:为组件添加 SEO 优化功能。
友情赞助
大家有空闲,帮忙试用下,国货抖音出品的AI编程代码插件,比比看GitHub Copilot那个好**^-^**
(适用JetBrains全家桶系列、Visual Studio家族IDE工具安装等主流IDE工具,支持100+种编程语言)
帮忙去助力>>