高階組件(HOC)

React官方定義高階組件的概念是:

A higher-order component is a function that takes a component and returns a new component.

通常情況下,實現(xiàn)高階組件的方式有以下兩種:
屬性代理(Props Proxy)
反向繼承(Inheritance Inversion)

屬性代理

實質(zhì)上是通過包裹原來的組件來操作props,舉個簡單的例子:

import React, { Component } from 'React';
//高階組件定義
const HOC = (WrappedComponent) =>
  class WrapperComponent extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
}
//普通的組件
class WrappedComponent extends Component{
    render(){
        //....
    }
}

//高階組件使用
export default HOC(WrappedComponent)

我們可以看見函數(shù)HOC返回了新的組件(WrapperComponent),這個組件原封不動的返回作為參數(shù)的組件(也就是被包裹的組件:WrappedComponent),并將傳給它的參數(shù)(props)全部傳遞給被包裹的組件(WrappedComponent)。這么看起來好像并沒有什么作用,其實屬性代理的作用還是非常強大的。

操作props

我們看到之前要傳遞給被包裹組件WrappedComponent的屬性首先傳遞給了高階組件返回的組件(WrapperComponent),這樣我們就獲得了props的控制權(quán)(這也就是為什么這種方法叫做屬性代理)。我們可以按照需要對傳入的props進(jìn)行增加、刪除、修改(當(dāng)然修改帶來的風(fēng)險需要你自己來控制),舉個例子:

const HOC = (WrappedComponent) =>
    class WrapperComponent extends Component {
        render() {
            const newProps = {
                name: 'HOC'
            }
            return <WrappedComponent
                {...this.props}
                {...newProps}
            />;
        }
    }

在上面的例子中,我們?yōu)楸话M件(WrappedComponent)新增加了固定的name屬性,因此WrappedComponent組件中就會多一個name的屬性。

抽象state

屬性代理的情況下,我們可以將被包裹組件(WrappedComponent)中的狀態(tài)提到包裹組件中,一個常見的例子就是實現(xiàn)不受控組件到受控的組件的轉(zhuǎn)變

class WrappedComponent extends Component {
    render() {
        return <input name="name" {...this.props.name} />;
    }
}

const HOC = (WrappedComponent) =>
    class extends Component {
        constructor(props) {
            super(props);
            this.state = {
                name: '',
            };

            this.onNameChange = this.onNameChange.bind(this);
        }

        onNameChange(event) {
            this.setState({
                name: event.target.value,
            })
        }

        render() {
            const newProps = {
                name: {
                    value: this.state.name,
                    onChange: this.onNameChange,
                },
            }
            return <WrappedComponent {...this.props} {...newProps} />;
        }
    }

上面的例子中通過高階組件,我們將不受控組件(WrappedComponent)成功的轉(zhuǎn)變?yōu)槭芸亟M件.

用其他元素包裹組件

    render(){
        <div>
            <WrappedComponent {...this.props} />
        </div>
    }

這種方式將被包裹組件包裹起來,來實現(xiàn)布局或者是樣式的目的。

在屬性代理這種方式實現(xiàn)的高階組件,以上述為例,組件的渲染順序是: 先WrappedComponent再WrapperComponent(執(zhí)行ComponentDidMount的時間)。而卸載的順序是先WrapperComponent再WrappedComponent(執(zhí)行ComponentWillUnmount的時間)。

高階組件的用法,其實就是封裝個函數(shù)將傳入的組件添加上數(shù)據(jù),直接導(dǎo)出即可,我們常用的react-redux 中的 connect(Children) 一個道理,封裝完將數(shù)據(jù)導(dǎo)入到組件當(dāng)中,組件相應(yīng)的具有數(shù)據(jù),以及具有了dispatch方法,就是這么個封裝。
話不多說直接上個小栗子:

class Parents extends Component {
  constructor(props) {
    super(props);
      this.state = {
         parentsSourse: '我是父組件數(shù)據(jù)'
      }
  }
  render() {
    <>
      <Children />
      這是父組件,相當(dāng)于我們的外層組件
    </>  
  }    
}    
class Children  extends Component {
   render() {
      <>
         這是子組件,我們展示組件
      </>  
   }    
}

我們假如我們想讓父組件包含的組件都具有一個屬性值,這個值是 newType: true, 此時我們可以直接向下級 Childlren 傳遞,那么我們也可以封裝下父組件導(dǎo)出個高階組件,那么這個方法可以這么寫:

const Hoc_component = (HocCompoent) =>  {
   return  class NewComponent extends React.Component{
      constructor(props){
         super(props);
         this.state={}
      }
    
       render() {
            const  props = { newType: true } 
            return <HocCompoent {...this.props}  {...props}/>
       }
   }
}    

// 此時所有的組件只要使用

Hoc_component(Children);   // 此時的子組件就具有了這個方法包裝的 newType屬性,我們可以去打印看下。

下面的例子,我們把兩個組件相似的生命周期方法提取出來,通過包裝,能夠節(jié)省非常多的重復(fù)代碼。

// CommentList
class CommentList extends React.Component {
  constructor(props) {
    super(props);
    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的需求也不同,但是他們有很多的內(nèi)容是相似的:

  • 在組件渲染之后監(jiān)聽DataSource
  • 在監(jiān)聽器里面調(diào)用setState
  • 在unmout的時候刪除監(jiān)聽器

在大型的工程開發(fā)里面,這種相似的代碼會經(jīng)常出現(xiàn),那么如果有辦法把這些相似代碼提取并復(fù)用,對工程的可維護(hù)性和開發(fā)效率可以帶來明顯的提升。
使用HOC我們可以提供一個方法,并接受不了組件和一些組件間的區(qū)別配置作為參數(shù),然后返回一個包裝過的組件作為結(jié)果。

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} />;
    }
  };
}

然后我們就可以通過簡單的調(diào)用該方法來包裝組件:

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

注意:在HOC中我們并沒有修改輸入的組件,也沒有通過繼承來擴展組件。HOC是通過組合的方式來達(dá)到擴展組件的目的,一個HOC應(yīng)該是一個沒有副作用的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容