React高階元件

kyooo0發表於2020-12-27

前段時間在工作中寫Hybrid頁面時遇到了這樣的一個場景,公司需要一系列的活動元件,在每個元件註冊的時候都需要呼叫App端提供的一個介面。一開始也考慮了幾種方式,包括mixin、元件繼承以及react高階元件。但經過了種種衡量,最後選擇使用了高階元件的做法。

1、Mixins的缺點

React官方已不推薦使用Mixins的技術來實現程式碼的重用,Mixins技術有一系列的缺點,首先Mixins會造成命名衝突,我們通過以下的方式來注入Mixins:

var myMixins = require('myMixins');

var Button = React.createClass({
    mixins: [myMixins],
    
    // ...
})
複製程式碼

如果你需要注入多個mixins,其中一個是自己的,另外的可能是第三方的。那有可能在兩個mixins裡使用了相同名稱的方法,這會使得其中的一個不起作用,而你能做的只有修改其中一個方法的名稱。另一方面,一個mixins一開始可能是非常簡單的,僅僅需要實現某一個功能,但當業務越加的複雜,需要往其中加入更多的方法的時候,就會變得非常複雜。要深入瞭解mixins的缺點,可以檢視官方部落格。

2、元件繼承

對於我自己來說這種方法以前使用的比較多,先建立一個BaseComponent,在其中實現一系列公共的方法,其後的每個元件都繼承於這個元件,但缺點是不夠靈活,在基礎元件中只能實現一些比較固定的方法,而對於每個元件的定製化會有很大的限制。

3、React高階元件

由於mixins的一系列缺點,React官方也意識到使用mixins所帶來的痛點遠遠高於技術本身產生的優點,而高階元件便可以代替mixins,而且當深入之後它還有著更加豐富的用法。

高階元件(HOC)是React中對元件邏輯進行重用的高階技術。但高階元件本身並不是React API。它只是一種模式,這種模式是由React自身的組合性質必然產生的。

高階函式

說到高階元件,就先得說到高階函式了,高階函式是至少滿足下列條件的函式:

1、接受一個或多個函式作為輸入
2、輸出一個函式

在javascript這門函式為一等公民的語言中,高階函式的使用還是非常之多的,像我們平時的回撥函式等等,都用到了高階函式的知識。我們先來看一個簡單的高階函式

var fun = function(x, y) {
    return x + y;
}
複製程式碼

fun是一個函式,下面我們將整個函式作為引數傳遞給另一個函式

var comp = function(x, y, f) {
    return f(x,y);
}
複製程式碼

驗證一下

comp(1,2,fun) // 3
複製程式碼
高階元件定義

類比高階函式的定義,高階元件就是接受一個元件作為引數,在函式中對元件做一系列的處理,隨後返回一個新的元件作為返回值。

我們先定義一個高階元件BaseActivity

const BaseActivity = (WrappedComponent) => {
  return class extends Component {
    render() {
      return (
        <section>
          <div>我的包裹元件</div>
          <WrappedComponent />
        </section>
        
      )
    }
  }
}
複製程式碼

元件接受一個被包裹的元件作為引數,返回了一個經過處理的匿名元件。 在其他元件中使用這個高階元件

class Example extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      width: '100%',
      height: '100%'
    }
  }

  componentWillMount() {
    if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
      return;
    } else {
      this.setState({
        width: '375px',
        height: '640px'
      })
    }
  }

  render() {
    let { width, height } = this.state;
    return (
      <div className="activity">
        <div className="activity-content" style={{ width, height }}>
          <button className="btn">參加活動</button>
        </div>
      </div>
    )
  }
}

export default BaseActivity(Example);
複製程式碼

具體用法就是在export 元件的時候,使用BaseActivity函式來包裹這個元件,看下輸出的react dom內容

image

在Example元件外面包裹了一個匿名元件。

引數

既然高階元件是一個函式,我們就可以向裡面傳遞我們需要的引數

const BaseActivity = (WrappedComponent, title) => {
  return class extends Component {
    render() {
      return (
        <section>
          <div>{title}</div>
          <WrappedComponent />
        </section>
        
      )
    }
  }
}
複製程式碼

在Example中這樣export

export default BaseActivity(Example, '這是高階元件的引數');
複製程式碼

我們看下輸出的react dom

image

可以看到引數已經傳遞進去了。

當然還可以這樣用(柯里化)

const BaseActivity (title) => (WrappedComponent) => {
  return class extends Component {
    render() {
      return (
        <section>
          <div>{title}</div>
          <WrappedComponent />
        </section>
        
      )
    }
  }
}
複製程式碼

在Example中這樣export

export default BaseActivity('這是高階元件的引數')(Example);
複製程式碼

這種用法在ant-design的表單以及redux的connect中我們都可以看到

// ant
const WrappedDemo = Form.create()(Demo)

// redux
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
複製程式碼

高階元件還可以擴充套件原元件的props屬性,如下所示:

const BaseActivity (title) => (WrappedComponent) => {
  return class extends Component {
    render() {
      const newProps = {
          id: Math.random().toString(8)
      }
      return (
        <section>
          <div>{title}</div>
          <WrappedComponent {...this.props} {...newProps}/>
        </section>
      )
    }
  }
}
複製程式碼

看下輸出的react dom

image

高階元件的缺點

高階元件也有一系列的缺點,首先是被包裹元件的靜態方法會消失,這其實也是很好理解的,我們將元件當做引數傳入函式中,返回的已經不是原來的元件,而是一個新的元件,原來的靜態方法自然就不存在了。如果需要保留,我們可以手動將原元件的方法拷貝給新的元件,或者使用hoist-non-react-statics之類的庫來進行拷貝。

結語

高階函式對於初學者來說可能不太好理解,但當你深入其中,瞭解其中的原理之後,我們可以使用高階函式來完成很多的工作。

如果喜歡請關注我的Blog,給個Star吧,會定期分享一些JS中的知識,^_^

相關文章