什么是React高阶组件?
反应 高阶组件(也称为HOC)是一种高级的React技术,用于重用组件逻辑。高阶组件不是react API的一部分。这是从React的组成性质中得出的一种模式。
基本上,React高阶组件是一个接受组件并返回新组件的函数。
HOC在第三方React库中很常见,例如Redux的 连接 和中继的 createFragmentContainer。
Use 反应 Higher Order 零件 For Cross Cutting Concerns
组件是React中代码重用的主要单元。但是,您会发现一些模式不适用于传统组件。
例如,您有一个CommentList组件,该组件可帮助您订阅外部数据源以呈现评论列表:
代码如下:
class 评论列表 扩展了React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
// “DataSource”是一些全球数据源
comments:DataSource.getComments()
};
}
componentDidMount(){
//订阅更改
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount(){
// Clean up listener
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
//每当数据源更改时更新组件状态
this.setState({
comments:DataSource.getComments()
});
}
render() {
return (
<div>
{this.state.comments.map((comment)=> (
<评论comment = {comment}键= {comment.id} />
))}
</div>
);
}
}
下一步是编写用于订阅单个博客文章的组件。这遵循大致相似的模式。
类BlogPost扩展了React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
blogPost:DataSource.getBlogPost(props.id)
};
}
componentDidMount(){
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount(){
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
blogPost:DataSource.getBlogPost(this.props.id)
});
}
render() {
return <TextBlock text = {this.state.blogPost} />;
}
}
评论列表 和BlogPost在任何方面都不相同。它们帮助访问数据源的不同方法并呈现不同的输出。相似之处在于它们的实现-
– 挂载时,将更改侦听器添加到DataSorce
– 在侦听器内部,只要数据源发生更改,就调用setState
– 卸载时,删除更改侦听器。
在大型应用程序中,订阅DataSource和调用setState的相同过程会重复多次。我们想要一个抽象,使我们可以在一个地方定义此逻辑并在许多组件之间共享它。这就是高阶组件的目标。
我们可以编写函数来创建订阅DataSource的组件,例如CommentList和BlogPost。
让我们用Subscription命名函数:
const 评论列表 WithSubscription = withSubscription(
CommentList,
(DataSource) =>DataSource.getComments()
);
const 博客文章 WithSubscription = withSubscription(
BlogPost,
(数据源,道具)=>DataSource.getBlogPost(props.id)
);
第一个参数是包装的组件。第二个参数在给定了数据源和当前道具的情况下检索我们感兴趣的数据。
什么时候 评论列表 WithSubscription 和 博客文章 WithSubscription 被渲染 评论列表 和 博客文章 将通过 数据 用从中获取的最新数据的道具 数据源:
c //此函数需要一个组件…
withSubscription(WrappedComponent,selectData){
// …并返回另一个组件…
返回类扩展了React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
数据:selectData(DataSource,props)
};
}
componentDidMount(){
// …负责订阅…
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount(){
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
数据:selectData(DataSource,this.props)
});
}
render() {
// …并用新数据渲染包装的组件!
//注意,我们通过了其他道具
return <WrappedComponent 数据 = {this.state.data} {…this.props} />;
}
};
}
请注意,HOC不会修改输入组件,也不会使用继承来复制其行为。 HOC而是通过将原始组件包装在容器组件中来组成原始组件。 HOC是纯函数,没有副作用。
Don’t Mutate the Original 零件. Use Composition.
抵制在HOC中修改组件原型的诱惑。
函数logProps(InputComponent){
InputComponent.prototype.componentDidUpdate = function(prevProps){
console.log(‘Current props: ‘, this.props);
console.log(‘Previous props: ‘, prevProps);
};
// The fact that we’重新返回原始输入表明它具有
// been mutated.
返回InputComponent;
}
//每当收到道具时,EnhancedComponent都会记录
const EnhancedComponent = logProps(InputComponent);
突变HOC是一个泄漏的抽象,并带有它们自己的问题。消费者必须知道如何实现它们,以避免与其他HOC发生冲突。
HOC应通过将输入组件包装在容器组件中来使用组成而不是突变。
函数logProps(WrappedComponent){
返回类扩展了React.Component {
componentDidUpdate(prevProps){
console.log(‘Current props: ‘, this.props);
console.log(‘Previous props: ‘, prevProps);
}
render() {
//将输入组件包装在容器中,而不对其进行突变。好!
return <WrappedComponent {…this.props} />;
}
}
}
这个新的HOC具有与变异版本相同的功能,但避免了引起潜在冲突的所有原因。因为这是一个纯函数,所以它与其他HOC甚至与自身兼容。
Container 零件s 是将责任划分为高级别和低级别问题的策略的一部分。 HOC将容器用作其实现的一部分。
约定:将无关的道具传递给被包裹的组件
HOC向组件添加功能,这些功能不会更改其合同。 HOC需要通过与其特定关注无关的道具。大多数HOC使用的方法与此类似:
render(){
//滤除此HOC特有的多余道具’t be
// passed through
const { extraProp, …passThroughProps} = this.props;
//将道具注入到包装好的组件中。这些通常是状态值或
// instance methods.
const injectionProp = someStateOrInstanceMethod;
//将道具传递给包裹的组件
return (
<WrappedComponent
injectionProp = {injectedProp}
{…passThroughProps}
/>
);
}
该约定有助于确保HOC尽可能灵活和可重用。
约定:最大化可组合性
并非所有HOC看起来都一样。有时他们只接受单个参数,即包装的组件。
const NavbarWithRouter = withRouter(Navbar);
通常,HOC接受其他参数。在此示例中,配置对象用于指定组件的数据依赖性。
const CommentWithRelay = Relay.createContainer(Comment,config);
HOC的最常见签名如下所示:
// 反应 Redux’s `connect`
const ConnectedComment = 连接 (commentSelector,commentActions)(CommentList);
当我们将其分解时,更容易看到正在发生的事情:
// 连接 是一个返回另一个函数的函数
const Enhance = 连接 (commentListSelector,commentListActions);
//返回的函数是一个HOC,它返回一个已连接的组件
//到Redux商店
const ConnectedComment =增强(CommentList);
换句话说,我们需要连接一个返回高阶组件的高阶函数。
单参数HOC具有签名Component =>零件。输出类型与输入相同的函数实际上是。简单。一起组成。
//而不是这样做…
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
// …您可以使用功能组合实用程序
// compose(f,g,h)与(…args) => f(g(h(…args)))
const Enhance = compose(
//这些都是单参数HOC
withRouter,
连接(commentSelector)
)
const EnhancedComponent =增强(WrappedComponent)
包装显示名称以便于调试
由React高阶组件创建的容器组件会显示在React开发人员工具中,并且为了简化调试,我们需要选择一个显示名称来表明它是HOC的最终产品。
withSubscription(WrappedComponent)的功能{
WithSubscription类扩展了React.Component {/ *… * /}
WithSubscription.displayName =`WithSubscription($ {getDisplayName(WrappedComponent)})`;
返回WithSubscription;
}
函数getDisplayName(WrappedComponent){
返回WrappedComponent.displayName || 包装组件.name ||‘Component’;
}
不要在render方法内使用HOC
如果从render返回的组件与之前的render组件相同,React会通过用新的subtree替换它来自动更新subtree。如果它们不相等,则先前的子树将完全卸载。
这在HOC中很重要,因为这意味着您无法使用render方法将HOC应用于组件:
render(){
//在每个渲染器上创建一个新版本的EnhancedComponent
// EnhancedComponent1!== EnhancedComponent2
const EnhancedComponent =增强(MyComponent);
//每次导致整个子树的卸载/重新安装!
return <EnhancedComponent />;
}
重新安装组件会导致组件及其所有子代的状态丢失。
在极少数情况下,需要动态应用HOC,它也可以在组件的生命周期方法或其构造函数中完成。
必须复制静态方法
有时,在React组件上定义静态方法很有用。
将HOC应用于组件时,原始组件将包装在容器组件中。这意味着新组件没有原始组件的任何静态方法。
//定义一个静态方法
包装组件.staticMethod = function(){/ *…*/}
//现在应用HOC
;
//增强组件没有静态方法
typeof EnhancedComponent.staticMethod ===‘undefined’ // true
为了解决这个问题,您可以在返回方法之前将方法复制到容器中。
功能Enhance(WrappedComponent){
class Enhance扩展React.Component {/ *…*/}
//必须确切知道要复制的方法-
Enhance.staticMethod = 包装组件.staticMethod;
return Enhance;
另一种可能的解决方案是将静态方法与组件本身分开导出。
// 代替…
MyComponent.someFunction = someFunction;
导出默认的MyComponent;
// …分别导出方法…
导出{someFunction};
// …并在使用模块中导入
从导入MyComponent,{someFunction}‘./MyComponent.js’;
裁判没有通过
尽管反应高阶组件的约定是将所有prop传递给包装的组件,但这对ref无效。那是因为ref就像由React处理的键一样。如果将ref添加到其成分是HOC结果的元素,则ref引用最外层容器组件的实例,而不是包装的组件。
解决此问题的方法是使用 反应 .forwardRef API(由React 16.3引入)。