1.在React中higher-order component (HOC)是一種重用組件邏輯的高級技術。HOC不是React API中的一部分。HOC是一個函數,該函數接收一個組件而且返回一個新組件。在React中,組件是代碼複用的基本單位。javascript
2.爲了解釋HOCs,舉下面兩個例子java
CommentList組件會渲染出一個comments列表,列表中的數據來自於外部。
class CommentList extends React.Component { constructor() { super(); this.handleChange = this.handleChange.bind(this); this.state = { // "DataSource" is some global data source comments: DataSource.getComments() }; } componentDidMount() { // Subscribe to changes DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // Clean up listener DataSource.removeChangeListener(this.handleChange); } handleChange() { // Update component state whenever the data source changes this.setState({ comments: DataSource.getComments() }); } render() { return ( <div> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ); } }
接下來是BlogPost組件,這個組件用於展現一篇博客信息算法
class BlogPost extends 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} />; } }
這兩個組件是不同的,它們調用了DataSource的不一樣方法,而且它們的輸出也不同,可是它們中的大部分實現是同樣的:app
1.裝載完成後,給DataSource添加了一個change listener
2.當數據源發生變化後,在監聽器內部調用setState
3.卸載以後,移除change listener函數
能夠想象在大型應用中,相同模式的訪問DataSource和調用setState會一次又一次的發生。咱們但願抽象這個過程,從而讓咱們只在一個地方定義這個邏輯,而後
在多個組件中共享。工具
接下來咱們寫一個建立組件的函數,這個函數接受兩個參數,其中一個參數是組件,另外一個參數是函數。下面調用withSubscription函數this
const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) );
調用withSubscription傳的第一個參數是wrapped 組件,第二個參數是一個函數,該函數用於檢索數據。
當CommentListWithSubscription和BlogPostWithSubscription被渲染,CommentList和BlogPost會接受一個叫作data的prop,data中保存了當前
從DataSource中檢索出的數據。withSubscription代碼以下:spa
// This function takes a component... function withSubscription(WrappedComponent, selectData) { // ...and returns another component... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ... that takes care of the subscription... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... and renders the wrapped component with the fresh data! // Notice that we pass through any additional props return <WrappedComponent data={this.state.data} {...this.props} />; } }; }
HOC並無修改輸入的組件,也沒有使用繼承去重用它的行爲。HOC只是一個函數。wrapped 組件接受了容器的因此props,同時還接受了一個新的prop(data),data
用於渲染wrapped 組件的輸出。HOC不關心數據怎麼使用也不關心數據爲何使用,wrapped組件不關心數據是哪兒獲得。
由於withSubscription只是一個常規的函數,你能添加任意個數的參數。例如,你能讓data prop的名字是可配置的,從而進一步將HOC與wrapped組件隔離。
或者接受一個配置shouldComponentUpdate,或者配置數據源的參數prototype
使用高階組件時有些須要注意的地方。component
1.不要修改原始組件,這一點很重要
有以下例子:
function logProps(InputComponent) { InputComponent.prototype.componentWillReceiveProps = function(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); }; // The fact that we're returning the original input is a hint that it has // been mutated. return InputComponent; } // EnhancedComponent will log whenever props are received const EnhancedComponent = logProps(InputComponent);
這裏存在一些問題,1.輸入的組件不能與加強的組件單獨重用。2.若是給EnhancedComponent應用其餘的HOC,也會改變componentWillReceiveProps。
這個HOC對函數類型的組件不適用,由於函數類型組件沒有生命週期函數
HOC應該使用合成代替修改——經過將輸入的組件包裹到容器組件中。
function logProps(WrappedComponent) { return class extends React.Component { componentWillReceiveProps(nextProps) { console.log('Current props: ', this.props); console.log('Next props: ', nextProps); } render() { // Wraps the input component in a container, without mutating it. Good! return <WrappedComponent {...this.props} />; } } }
這個新的logProps與舊的logProps有相同的功能,同時新的logProps避免了潛在的衝突。對class類型的組件和函數類型額組件一樣適用。
2.不要在render方法中使用HOCs
React的diff算法使用組件的身份去決定是應該更新已存在的子樹仍是拆除舊的子樹並裝載一個新的,若是從render方法中返回的組件與以前渲染的組件恆等(===),
那麼React會經過diff算法更新以前渲染的組件,若是不相等,以前渲染的子樹會徹底卸載。
render() { // A new version of EnhancedComponent is created on every render // EnhancedComponent1 !== EnhancedComponent2 const EnhancedComponent = enhance(MyComponent); // That causes the entire subtree to unmount/remount each time! return <EnhancedComponent />; }
在組件定義的外部使用HOCs,以致於結果組件只被建立一次。在少數狀況下,你須要動態的應用HOCs,你該在生命週期函數或者構造函數中作這件事
3.靜態方法必須手動複製
有的時候在React組件上定義靜態方法是很是有用的。當你給某個組件應用HOCs,雖然原始組件被包裹在容器組件裏,可是返回的新組件不會有任何原始組件的靜態
方法。
// Define a static method WrappedComponent.staticMethod = function() {/*...*/} // Now apply an HOC const EnhancedComponent = enhance(WrappedComponent); // The enhanced component has no static method typeof EnhancedComponent.staticMethod === 'undefined' // true
爲了讓返回的組件有原始組件的靜態方法,就要在函數內部將原始組件的靜態方法複製給新的組件。
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // Must know exactly which method(s) to copy :( // 你也可以藉助第三方工具 Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }
4.容器組件上的ref不會傳遞給wrapped component
雖然容器組件上的props能夠很簡單的傳遞給wrapped component,可是容器組件上的ref不會傳遞到wrapped component。若是你給經過HOCs返回的組件設置了ref,這個ref引用的是最外層容器組件,而非wrapped 組件