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