React-HOC了解一下

2,165 阅读4分钟

用技术改变生活,用生活完善技术。来自携程(旅悦)一枚向全栈方向努力奔跑的前端工程师。 微信同步: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';