如何将一个 React 组件包装到另一个组件中
介绍
在 React 中,高阶组件(HOC)是接受一个组件并以某种方式增强它之后输出一个新组件的函数:
const EnhancedHOCComponent = hoc(OriginalReactComponent);
当您需要将函数、状态和补充数据插入组件或使用样式或进一步的 JSX 对组件进行分组时,HOC 会发挥重要作用。使用 HOC 使我们能够仅扩展需要扩展的组件,同时通过分离组件实现和逻辑来保持代码库模块化。
这里要记住的是,React 组件被设计为可重复使用;它们应该专注于单一目的,并应包含该特定目的的逻辑实现。HOC 使我们能够坚持这些要求。
本指南旨在详细说明 HOC 是什么以及它们在 React 中的使用方法。它还包含其应用示例。
HOC 的高层视图
许多模块都采用 HOC 设计模式,将模块中的功能插入到组件中。这是 HOC 最常见的用例。
React 中大多数广泛使用的包都采用了 HOC 设计模式,如以下示例所示:
- react-cookies使我们能够从任何组件存储和检索 cookie,其功能是使用withcookies() HOC 插入的。
- react-redux为我们提供了connect() HOC,它将状态和动作作为 props 插入,使组件能够访问 Redux 存储。
- react-router-dom有一个withRouter() HOC,它为组件提供路由器历史记录、位置和最近匹配的<Route/>。
HOC 大多以with或get为前缀。以with为前缀的 HOC大多用于注入功能,而以get为前缀的 HOC大多用于在原始组件中注入数据。
一般来说,在我们的组件定义之后,HOC 会在导出时应用于任何组件。
import { withSomeFunctions } from 'some-module';
class OriginalReactComponent extends React.Component {
...
}
export default withSomeFunctions(OriginalReactComponent);
将 HOC 应用于任何组件都非常简单;我们只需导入所需的 HOC 函数并用 HOC 包装原始组件即可。HOC 的实现也与任何函数相同。
export function withSomeFunctions(OriginalReactComponent) {
return class extends React.Component {
// we can make some enhancements here
...
render() {
//return original react component with additional props
return <OriginalReactComponent {...this.props} />
}
}
}
因此,使用我们原来的组件创建一个新类,而无需更新原始类。实际上发生的事情是:
- 原始组件作为 HOC 的输入提供。
- HOC 函数输出一个类组件,其中包含我们想要进行的改进,例如附加生命周期方法、API 端点等。
- render()输出原始组件以及我们可能想要添加的任何额外道具或我们可能想要包含的任何 JSX。
更新原始组件或更改其原型被认为是不好的编码习惯。相反,最佳做法是遵循组合模式,将原始组件包装在新的容器类中 — 因此有以下术语:
- 包装组件是原始组件。
- 容器组件是在 HOC 中定义的新类。
请注意,HOC 始终返回一个类组件。如果我们将任何函数式组件传入withMyFunction(),则将返回一个新的类组件。本指南中记录了有关实现函数式 HOC 的说明。
由于 HOC 是函数,我们可以使用单独的文件中的导出函数定义它们,并将其作为任何其他函数导入:
// src/hocexample/index.js
import React from 'react';
export function withSomeFunctions(WrappedComponent) {
return class extends React.Component {
...
}
}
// src/components/OriginalReactComponent.js
import { withSomeFunctions } from '../hocexample';
...
道具操控
上面的例子表明,可以通过在返回语句中使用扩展运算符将额外的 props 传递到我们包装的组件中:
render() (
<WrappedComponent {...this.props} />
);
这是确保我们的<WrappedComponent />接收通过 JSX 传递到我们的 HOC 的任何新 props 的重要第一步。
不过,通过 JSX 传递 props 并不是唯一的选择。假设我们的 HOC 的目的是插入一些函数来执行特定任务,例如,为 Web 服务提供 API 调用。对于此用例,我们将导入这些函数,然后将它们作为 props 包含在我们的<WrappedComponent />中:
// src/hocexample/index.js
import React from 'react';
import { f1, f2 } from '../someFunctions';
export function withSomeFunctions(WrappedComponent) {
return class extends React.Component {
render() {
const updatedProps = {
f1: f1,
f2: f2
};
return <WrappedComponent {...this.props} {...updatedProps} />
}
}
}
另一方面,如果我们有一些不想传递给<WrappedComponent />的 props ,我们可以通过过滤掉不必要的 props 来解决这个问题,然后再次使用扩展运算符来对剩余的 props 进行分组:
render() {
const { notNeededProp, someOtherProps, ...finalProps } = this.props;
return <WrappedComponent {...finalProps} />
}
状态抽象
HOC 的另一个用途是将状态插入组件。具有状态的 React 类组件具有类似的功能。以下示例插入用于存储名字的状态。它还有一个处理程序函数来更新它。
// src/hocexample/index.js
import React from 'react';
export function withSomeFunctions(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: ''
};
}
onFirstNameUpdate = (event) => {
this.setState({ firstName: event.target.value });
}
render() {
return
<WrappedComponent
{...this.props}
firstName={this.state.firstName}
onFirstNameUpdate={this.onFirstNameUpdate}
/>
}
}
}
我们无需定义newProps对象,只需将与状态相关的 props 直接传递给WrappedComponent即可。HOC 可用于从全局存储中注入值。
constructor(props) {
super(props);
this.state = {
firstName: store.get('firstName') || {}
};
}
...
render() {
return
<WrappedComponent
{...this.props}
{...this.state}
onFirstNameUpdate={this.onFirstNameUpdate}
/>
}
}
使用 HOC 时的可重用性
为了避免重复,React 官方文档强调了 HOC 抽象,即订阅数据源,然后在每次数据发生变化时将检索到的数据插入到包装的组件中,此外还将其保存在 HOC 的状态中。对数据源本身的订阅由 HOC 的生命周期方法管理。
HOC 被称为withSubscription(),它允许传递DataSource对象来检索包装组件关注的任何内容,例如新闻帖子、评论列表、实时新闻提要、一些日志等。DataSource 的基础并没有真正记录在官方 React 文档中,并且它如何检索数据并监听更改(通过轮询、Web 套接字等)也不是很清楚。
但有趣的是,第二个参数被传递给了我们的 HOC,它是一个从 API 检索数据的函数:
// a data source for getting list of comments
(DS, props) => DS.getListOfComments()
//a data source for getting news post
(DS, props) => DS.getNewsPost(props.id)
//a data source for getting some logs
(DS, props) => DS.getLogs(props.id)
因此,在我们的 HOC 中插入了一个函数定义来检索数据。
componentDidMount和componentDidUnmount方法来处理监听器的初始化和终止,还有一个handleChange方法,每次数据改变时都会调用,从而提交组件的状态并再次渲染包装的组件。
这个用例可能有点难以理解。以下是关键点:
- 尽管在大多数情况下单个参数是最佳选择,但我们可以选择在 HOC 中插入多个参数。
- 函数也可以作为我们传递给 HOC 的参数的一部分,可以在 HOC 中调用。当我们想要将 HOC 链接到 Web 服务时,这会非常方便,确定要提供给包装组件的数据。
- 包装组件也可以与生命周期方法分组。管理数据订阅或监听事件是生命周期方法在 HOC 中的一些很好的例子。
实用工具
组件元数据是 HOC 的一个不太复杂的用例。它涉及检索包装组件的属性,这些属性随后可以作为 props 插入。
react-measure或react-with-available-width是一些很好的 HOC 示例,它们可以测量组件的宽度、高度、边界等。这两个包都有一个onResize方法,使我们能够响应上述任何指标的变化。它们对于高级 UX 自定义非常有用,例如对窗口滚动、调整大小等做出反应。
高阶函数或 HOF
根据目前的讨论,我们看到 HOC 接收一个组件并返回一个新组件。但这个概念可以进一步探索。假设我们有一个函数,它会返回另一个函数,然后我们可以使用它来返回我们改进的包装组件,从而为 HOC 添加另一层功能。
Redux 的connect()做同样的事情,从以下语法可以看出:
const MyEnhancedComponent =
connect(mapStateToProps, matchDispatchToProps)(WrappedComponent);
connect()返回一个函数并且它本身也是一个函数,传递给该结果函数的唯一参数是WrappedComponent。
根据上述定义,connect()可以称为高阶函数,或 HOF;其输出实际上是具有mapStatetoProps和matchDispatchtoProps的增强函数,然后返回一个 HOC。</fo
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~