接着介绍React中的高阶组件,上篇文章React中的高阶组件(一)
高阶组件(Higher-Order Component, HOC) 是React中为了复用组件的函数。一个 HOC 是一个函数,接收一个组件并返回一个增强版的组件。通过 HOC,开发者可以将通用逻辑提取到可复用的组件包装器中,避免代码重复。
5. withSuspense(React.lazy)
- 功能:React 中使用
React.lazy和Suspense实现组件的懒加载(Lazy Loading)。懒加载是一种优化页面性能的方法,通过延迟加载某些组件直到它们被真正需要时才进行加载,从而减少初始加载时间。
import React, { Suspense } from "react";
//**`React.lazy`** 是 React 提供的懒加载功能,用于动态导入组件。它接收一个函数作为参数,
//这个函数使用 `import()` 动态加载组件文件。这个函数的返回值是一个 Promise,`React.lazy`
//会将这个 Promise 转化为可以使用的 React 组件。
//`React.lazy(() => import("./LazyComponent"))` 表示 `LazyComponent` 组件不会在应用启动时立即加载,
//而是当 `LazyComponent` 第一次被渲染时,才会触发动态导入,从而减少初始加载时的 JavaScript 包体积。
const LazyComponent = React.lazy(() => import("./LazyComponent"));
const MyApp = () => (
//**`Suspense`** 是 React 中用于包裹懒加载组件的组件,它会在懒加载的组件加载完成之前显示
//一个备用的 UI(即 `fallback`)。
//`Suspense` 的 `fallback` 属性用于指定在等待懒加载组件加载时展示的内容。它可以是任何有效的 JSX 元素,
//例如:加载动画、加载文本等。在上面的代码中,当 `LazyComponent` 还未加载完成时,会显示 `<div>Loading...</div>` 。
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
懒加载的优势
懒加载可以帮助提升应用的性能,尤其在以下场景中:
- 首屏优化:如果某些组件不会立刻出现在首屏内容中(例如路由下的子页面组件),可以通过懒加载将它们延后加载,这样可以显著减小首屏加载时的 JavaScript 文件大小,加快页面的初始渲染速度。
- 按需加载:对于一些不常用的组件(例如图片查看器、视频播放器等),可以通过懒加载让这些组件在真正需要时再加载,避免加载无用的资源。
代码流程解释
- 初始化应用:当应用开始运行时,
LazyComponent组件并不会立即被加载。React.lazy会告诉 React 在需要的时候才加载这个组件。 - 首次渲染:当
MyApp组件渲染时,Suspense组件包裹了LazyComponent。因为LazyComponent还没有加载完成,所以Suspense的fallback内容会先显示出来。在这个例子中,<div>Loading...</div>将首先渲染。 - 懒加载组件加载完成:当
LazyComponent被成功加载后,React 会自动替换掉fallback占位符,并渲染真正的LazyComponent组件。
React.lazy 和 Suspense 的适用场景
React.lazy 和 Suspense 主要适用于大型应用中,以下是一些常见的使用场景:
- 路由组件的懒加载:在使用 React 路由时,只有当某个路径被访问时才去加载该路径对应的组件。
- 第三方库或模块的按需加载:一些大型的第三方库(例如图表库、富文本编辑器等),可以通过懒加载来避免初始加载时加载所有的库,只有当用户实际使用到这些功能时再去加载对应的模块。
例如,以下是一个常见的路由懒加载的场景:
import React, { Suspense } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
const Home = React.lazy(() => import("./Home"));
const About = React.lazy(() => import("./About"));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
);
在这个例子中,Home 和 About 组件只有在路由被匹配时才会被加载,这样可以显著减少应用的初始加载时间。
注意事项
- 错误处理:默认情况下,
React.lazy的加载是异步的,可能会因为网络问题等原因加载失败。为了处理这种情况,可以配合React.ErrorBoundary进行错误处理,捕获加载错误并给用户提供反馈。
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong...</div>;
}
return this.props.children;
}
}
const App = () => (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
兼容性:React.lazy 和 Suspense 目前只支持组件的懒加载,不支持非组件的懒加载(如函数或模块)。如果需要加载其他类型的模块,建议使用 Webpack 的动态 import()。
总结
React.lazy:用于懒加载组件,它通过动态导入的方式按需加载组件,从而提升页面性能。Suspense:用于处理懒加载组件的占位符渲染,当组件加载时展示一个fallback内容。- 使用场景:懒加载非常适合大型应用,尤其是当一些组件只在特定条件下使用时,可以通过懒加载优化初始加载时间,提升用户体验。
6. withLoading(加载状态处理)
- 功能:
React.lazy和Suspense目前只支持组件的懒加载,非组件的懒加载(如函数或模块)可以使用withLoading。 - 示例:
const withLoading = (WrappedComponent) => {
return class extends React.Component {
state = { loading: true };
componentDidMount() {
fetchData().then(() => this.setState({ loading: false }));
}
render() {
if (this.state.loading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...this.props} />;
}
};
};
export default withLoading;
1. withLoading 高阶组件
- 参数
WrappedComponent:这是被包装的组件,withLoading会为这个组件添加加载状态功能。 - 返回一个新的类组件:高阶组件通常会返回一个类或函数组件,目的是在返回的组件中注入额外的功能。在这个例子中,返回了一个扩展自
React.Component的类组件。
2. state = { loading: true }
- 该组件有一个状态
loading,初始值为true,表示组件在刚加载时处于加载中状态。 loading状态将控制组件是否显示加载提示或实际内容。
3. componentDidMount()
- 数据加载逻辑:在组件挂载时会调用
fetchData()函数(这个函数需要根据实际情况定义),通常用于获取异步数据。获取完数据后,通过this.setState({ loading: false })来更新loading状态为false,表示加载完成。 - 加载完成后,组件会重新渲染,并显示真正的
WrappedComponent。
4. render()
-
在渲染函数中,根据
loading状态决定渲染什么内容。- 如果
loading为true,则渲染<div>Loading...</div>,即加载提示。 - 如果
loading为false,则渲染WrappedComponent,并将传递给withLoading的props全部传递给WrappedComponent。
- 如果
5. export default withLoading
- 这行代码将
withLoading高阶组件导出,供其他模块使用。
使用方法
假设你有一个简单的组件 MyComponent,你可以通过 withLoading 高阶组件为它添加加载状态:
import React from 'react';
import withLoading from './withLoading';
const MyComponent = () => {
return <div>My Component Content</div>;
};
export default withLoading(MyComponent);
在这个例子中,MyComponent 会在加载时显示 "Loading...",当数据加载完成后,才会显示 MyComponent 的内容。
延展fetchData()
为了让这个高阶组件更实用,fetchData() 通常会作为参数传入高阶组件,这样可以根据不同的需求动态传入不同的数据获取逻辑。以下是一个示例,包括如何使用 withLoading 包装组件并传入 fetchData 函数:
import React from 'react';
// 模拟的 fetchData 函数,返回一个 Promise,模拟异步数据获取
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Data loaded');
}, 2000); // 模拟2秒的延迟
});
};
// 高阶组件 withLoading
const withLoading = (WrappedComponent, fetchData) => {
return class extends React.Component {
state = { loading: true };
componentDidMount() {
fetchData().then(() => this.setState({ loading: false }));
}
render() {
if (this.state.loading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...this.props} />;
}
};
};
// 被包装的组件 MyComponent
const MyComponent = (props) => {
return <div>Component content goes here</div>;
};
// 使用 withLoading 包装 MyComponent 并传入 fetchData
const MyComponentWithLoading = withLoading(MyComponent, fetchData);
// 主应用组件
const App = () => {
return (
<div>
<h1>My Application</h1>
<MyComponentWithLoading />
</div>
);
};
export default App;
代码详解
-
fetchData函数:这是一个模拟的异步函数,返回一个Promise,并在2秒后 resolve 模拟的数据。这个函数将作为参数传递给withLoading高阶组件。 -
withLoading高阶组件:- 接受
WrappedComponent和fetchData作为参数。 - 在
componentDidMount生命周期方法中调用fetchData(),并在数据加载完成后将loading状态设置为false。 - 当
loading为true时显示 "Loading...",当加载完成后显示被包装的组件。
- 接受
-
MyComponent:这是一个简单的展示组件。通过withLoading包装后,加载完成后会显示它的内容。 -
MyComponentWithLoading:这是将MyComponent与fetchData组合后的新组件。它会先显示加载状态,然后在数据加载完成后显示MyComponent的内容。 -
App组件:主应用组件,渲染MyComponentWithLoading。
执行流程
- 应用启动时,
MyComponentWithLoading通过withLoading高阶组件包装。 withLoading调用fetchData,模拟数据加载,并显示加载中的提示。- 当
fetchData在2秒后 resolve 数据时,loading状态更新为false。 - 最终,加载完成后,
MyComponent的内容会替代加载中的提示被渲染出来。
运行后效果
- 应用会先显示
Loading...,等待 2 秒钟后,页面会显示Component content goes here。
7. withTranslation(i18n 国际化)
withTranslation 是来自 react-i18next 的高阶组件,用于在 React 应用中实现国际化。通过它,可以将翻译函数 t 传递给组件,以便轻松地使用多语言支持。
使用示例:
import React from 'react';
import { withTranslation } from 'react-i18next';
const MyComponent = ({ t }) => {
return <div>{t('welcome_message')}</div>;
};
export default withTranslation()(MyComponent);
解释:
- 这是一段函数式组件代码,使用了
withTranslation高阶组件。 withTranslation()将t函数注入到MyComponent组件的props中,t('welcome_message')表示从国际化资源中查找welcome_message对应的翻译内容并显示。t('welcome_message')会根据用户选择的语言来显示不同的文本。例如,如果用户选择了中文,它可能显示欢迎消息。
工作流程:
- 通过
withTranslation将i18n的翻译函数t注入MyComponent。 - 在 JSX 代码中调用
t('welcome_message')以获取翻译。 - 组件会根据当前语言的设置,显示相应的翻译文本。
代码二(使用类组件):
import { withTranslation } from 'react-i18next';
class NavbarComponent extends React.Component {
render() {
const { t } = this.props;
return <div>{t('navbar.home')}</div>;
}
}
export default withTranslation()(NavbarComponent);
解释:
- 这是使用类组件的代码,功能与第一段代码相似。
- 同样通过
withTranslation()高阶组件,将t函数注入到NavbarComponent的props中,t('navbar.home')表示从国际化资源中查找navbar.home对应的翻译内容。 - 与第一段代码不同的是,这里使用了类组件,而不是函数式组件。
工作流程:
- 通过
withTranslation将t函数注入到类组件NavbarComponent中。 - 在
render方法中调用t('navbar.home')来获取翻译文本。 - 根据用户选择的语言,显示
navbar.home的相应翻译内容。
总结:
withTranslation()是 React 中常用的高阶组件,主要作用是将i18next的翻译功能注入到组件中。- 无论是函数式组件还是类组件,
withTranslation都可以无缝集成国际化功能,开发者可以根据项目需求选择使用哪种组件风格。
8. withSubscription
withSubscription 是一种常见的高阶组件模式,主要用于订阅数据源并将数据传递给被包装的组件。它可以从事件、WebSocket 或数据流中订阅数据,然后将这些数据注入到组件中。
这段代码定义了一个名为 withSubscription 的高阶组件(Higher-Order Component, HOC),其主要作用是为一个包装的组件 WrappedComponent 提供订阅机制,动态更新数据,并且将数据传递给被包装的组件。以下是对代码的详细介绍和工作原理的解析:
//也可以写成const withSubscription_myname ,进行自定义名称
const withSubscription = (WrappedComponent, selectData) => {
return class extends React.Component {
constructor(props) {
//`super()` 是用来调用父类的构造函数。
//在 ES6 类继承中,如果一个类继承了另一个类(例如 `class MyComponent extends React.Component`),
//那么在派生类的构造函数中必须首先调用 `super()`,才能访问 `this` 关键词。
super(props);
this.state = {
data: selectData(DataSource, props), // 初始化状态,使用 selectData 函数从 DataSource 中获取数据
};
}
componentDidMount() {
// 组件挂载后,订阅数据源的变化
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
// 组件卸载前,取消订阅
DataSource.removeChangeListener(this.handleChange);
}
handleChange = () => {
// 当数据源发生变化时,更新组件状态
this.setState({
data: selectData(DataSource, this.props),
});
};
render() {
// 将获取到的数据作为 props 传递给被包装的组件 WrappedComponent
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
};
代码功能
-
withSubscription高阶组件:这是一个 HOC,它接受两个参数:WrappedComponent:需要被增强的组件,即接收订阅数据的目标组件。selectData:这是一个函数,负责从DataSource中选择需要的数据,并根据props提供的上下文对数据进行选择。
-
构造函数(
constructor) :- 在组件的构造函数中,调用
selectData(DataSource, props)来初始化数据,并将其存储在组件的state中。
- 在组件的构造函数中,调用
-
componentDidMount生命周期方法:- 当组件挂载到 DOM 后,调用
DataSource.addChangeListener(this.handleChange)为数据源添加一个变化监听器。这样,数据源每次发生变化时,都会调用handleChange方法。
- 当组件挂载到 DOM 后,调用
-
componentWillUnmount生命周期方法:- 当组件即将从 DOM 卸载时,调用
DataSource.removeChangeListener(this.handleChange)来移除监听器,防止内存泄漏。
- 当组件即将从 DOM 卸载时,调用
-
handleChange方法:- 每当
DataSource中的数据发生变化时,handleChange被触发。它调用selectData再次从DataSource获取最新的数据,并通过setState更新组件的state,从而触发组件重新渲染。
- 每当
-
render方法:- 在
render中,WrappedComponent被渲染并接收当前组件的state.data和所有props。因此,WrappedComponent能够使用被订阅的数据。
- 在
应用场景与示例
假设我们有一个 CommentList 组件,它需要动态显示某个用户的评论列表,使用 withSubscription 包装后,可以让它自动订阅数据源的变化并实时更新评论列表。
// selectComments 是一个从 DataSource 获取评论数据的函数
const selectComments = (DataSource, props) => {
return DataSource.getCommentsByUserId(props.userId);
};
// 普通的组件
class CommentList extends React.Component {
render() {
const { data } = this.props; // 获取订阅的数据
return (
<ul>
{data.map((comment) => (
<li key={comment.id}>{comment.text}</li>
))}
</ul>
);
}
}
// 使用 withSubscription 高阶组件进行增强
const SubscribedCommentList = withSubscription(CommentList, selectComments);
export default SubscribedCommentList;
在这个示例中,SubscribedCommentList 是增强版的 CommentList,它会自动订阅数据源的变化,当 DataSource 中的评论数据发生变化时,组件会自动更新显示。
友情赞助
大家有空闲,帮忙试用下,国货抖音出品的AI编程代码插件,比比看GitHub Copilot那个好**^-^**
(适用JetBrains全家桶系列、Visual Studio家族IDE工具安装等主流IDE工具,支持100+种编程语言)
帮忙去助力>>