用技术改变生活,用生活完善技术。来自微播易一枚向全栈方向努力奔跑的前端工程师。 微信同步:wDxKn89
最近在公司接了一个老项目迁移React。在组件开发过程中,发现有一些组件的处理逻辑很类似。想在某一个地方来统一处理。进过思考和分析,发现HOC(higher-order component)很适合完成此类工作。 再次来总结HOC的概念和基本用法。
HOC本质
针对React开发来讲,component是页面的基本组成单位。一个页面可以由很多拥有各自处理逻辑的组件来组合。正如在开发工程中遇到的问题,某几个组件拥有类似的数据过程,是不是可以采用某一种机制或者方法来统一处理该数据处理。
所以HOC就出现了。
a higher-order component is a function that takes a component and returns a new component.
注意:HOC是一个function而这个函数接受一个组件,return被处理过的新组件。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
复制代码
Code实现
案例重现
现在有一个需求,现在有TestTableA,TestTableB,他们用于渲染table数据,同时接收的数据格式也类似。只是TestTableA比TestTableB多一列展示数据。
col1 | col2 | col3 |
---|---|---|
内容 | 内容 | 内容 |
内容 | 内容 | 内容 |
col1 | col2 |
---|---|
内容 | 内容 |
内容 | 内容 |
代码实现
现在采用HOC来统一处理table colum的拼装和数据的获取。(组件是用的Antd)
ReferencePriceFactory (HOC组件构建)
import React from 'react';
function ReferencePriceFactory(WrappedComponent,configInfo) {
return class extends React.Component {
constructor(props){
super(props);
}
render() {
//这里为了方便,直接假设用调用处将数据传入
const {tableData} = this.props;
renderContent
const renderContent = (text, row, index) => {
let value;
const obj = {
children: {},
props: {}
}
value = <span>{text}</span>
obj.children = value;
return obj;
}
let Columns = [{
title:'col1' ,
dataIndex:'type',
render: (text,record,index) =>renderContent(text,record,index)
}, {
title: 'col2',
dataIndex:'referencePrice',
render: (text,record,index) =>renderContent(text,record,index)
}];
let empty = {
colSpan:0,
render: (text, record, index) => {
let value = null;
const obj = {
children: value,
props: {},
};
obj.props.colSpan = 0;
return obj;
},
};
let extraColumns =configInfo? {
title: col3,
dataIndex:'playPrice',
render: (text,record,index) =>renderContent(text,record,index)
}:empty;
const finalColumns = [...Columns,extraColumns];
return <WrappedComponent {...this.props} finalColumns={finalColumns}/>;
}
}
}
export default ReferencePriceFactory;
复制代码
TestTableA/TestTableB代码实现
import React from 'react';
import { Table} from 'antd';
export default class TestTableA extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
let {finalColumns,tableData} = this.props;
return (
<span>
<Table dataSource={tableData} columns={finalColumns} pagination={false} />
</span>
)
}
}
复制代码
组件调用
const EnhancedTestTableA = ReferencePriceFactory(TestTableA,true);
const EnhancedTestTableB = ReferencePriceFactory(TestTableB,true);
export default class Test extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
const {record} = this.props;
return (
<span>
<EnhancedTestTableA record={record}/>
<EnhancedTestTableB record={record}/>
</span>
)
}
}
复制代码
在需要用到该组件的地方进行组件的处理,并调用。
HOC使用注意事项
不能修改原始组件
function logProps(InputComponent) {
//通过修改prototype来修改组件
InputComponent.prototype.componentWillReceiveProps = function(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
};
//此处其实已经修改了InputComponent了
return InputComponent;
}
// 组件调用
const EnhancedComponent = logProps(InputComponent);
复制代码
通过该方式来处理组件,最大的坏处就是如果还有另外一个HOC来对该组件进行加强(修改的是同一个方法),最后一次修改会对上一次加强的结果进行重写。
通过直接修改组件的方法,是弱抽象的。 如果想规避上述问题,构建一个HOC来加强组件是通过构建一个组件来调用需要被加强的组件。
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
}
复制代码
通过如上的分析会发现HOC和Container component处理方式很类似。
多个HOC组合使用
通过上文分析,HOC的本质就是一个用于返回被加强的组件的函数。所以如果一个组件需要进行不同维度的加强。就需要对组件进行多次HOC处理。
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
复制代码
但是如果对函数式编程有了解的话,就会对compose有过了解。 通过compose处理上述代码可以改写如下:
const enhance = compose(
withRouter,
connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
复制代码
Note
HOC是不能够在render方法中使用的。
React的diff算法通过判断component ID来决定是否更新存在的子树或者删除子树,并且重新加载一个新的。如果从render方法中返回的组件(===)原来的渲染的组件。但是HOC是一个函数,每次调用都会返回一个新的组件,所以render方法每次调用都会触发diff处理,并将原有的组件进行删除,重新加载一个新的组件。
组件的静态方法必须在HOC中重新调用
当你应用HOC通过调用原始组件通过加强来返回一个新的组件。这意味着新的组件没有原始组件的任何静态方法,所以想要在新的组件中使用静态方法的话,就需要在HOC中进行重新调用。
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
复制代码
或者通过将静态方法export出来,在调用出import
MyComponent.someFunction = someFunction;
export default MyComponent;
//单独导出
export { someFunction };
import MyComponent, { someFunction } from './MyComponent.js';
复制代码