用技術改變生活,用生活完善技術。來自攜程(旅悅)一枚向全棧方向努力奔跑的前端工程師。 微信同步: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';
複製程式碼