摘要:在項目中用好高階組件,可以顯著提高代碼質量。高階組件的定義類比于高階函數的定義。高階函數接收函數作為參數,并且返回值也是一個函數。
React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。1. 基本概念
高階組件是React 中一個很重要且比較復雜的概念,高階組件在很多第三方庫(如Redux)中都被經常使用。在項目中用好高階組件,可以顯著提高代碼質量。
高階組件的定義類比于高階函數的定義。高階函數接收函數作為參數,并且返回值也是一個函數。類似的,高階組件接收React組件作為參數,并且返回一個新的React組件。高階組件本質上也是一個函數,并不是一個組件,這一點一定不要弄錯。
2. 應用場景為什么React引入高階組件的概念?它到底有何威力?讓我們先通過一個簡單的例子說明一下。
假設有一個組件MyComponent,需要從LocalStorage中獲取數據,然后渲染數據到界面。我們可以這樣寫組件代碼:
import React, { Component } from "react" class MyComponent extends Component { componentWillMount() { let data = localStorage.getItem("data"); this.setState({data}); } render() { return{this.state.data}} }
代碼很簡單,但當有其他組件也需要從LocalStorage中獲取同樣的數據展示出來時,需要在每個組件都重復componentWillMount中的代碼,這顯然是很冗余的。下面讓我們來看看使用高階組件可以怎么改寫這部分代碼。
import React, { Component } from "react" function withPersistentData(WrappedComponent) { return class extends Component { componentWillMount() { let data = localStorage.getItem("data"); this.setState({data}); } render() { // 通過{...this.props} 把傳遞給當前組件的屬性繼續傳遞給被包裝的組件WrappedComponent return} } } class MyComponent2 extends Component { render() { return {this.props.data}} } const MyComponentWithPersistentData = withPersistentData(MyComponent2)
withPersistentData就是一個高階組件,它返回一個新的組件,在新組件的componentWillMount中統一處理從LocalStorage中獲取數據的邏輯,然后將獲取到的數據以屬性的方式傳遞給被包裝的組件WrappedComponent,這樣在WrappedComponent中就可以直接使用this.props.data獲取需要展示的數據了,如MyComponent2所示。當有其他的組件也需要這段邏輯時,繼續使用withPersistentData這個高階組件包裝這些組件就可以了。
通過這個例子,可以看出高階組件的主要功能是封裝并分離組件的通用邏輯,讓通用邏輯在組件間更好地被復用。高階組件的這種實現方式,本質上是一個裝飾者設計模式。
高階組件的參數并非只能是一個組件,它還可以接收其他參數。例如,組件MyComponent3需要從LocalStorage中獲取key等于name的數據,而不是上面例子中寫死的key等于data的數據,withPersistentData這個高階組件就不滿足我們的需求了。我們可以讓它接收額外的一個參數,來決定從LocalStorage中獲取哪個數據:
import React, { Component } from "react" function withPersistentData(WrappedComponent, key) { return class extends Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({data}); } render() { // 通過{...this.props} 把傳遞給當前組件的屬性繼續傳遞給被包裝的組件WrappedComponent return} } } class MyComponent2 extends Component { render() { return {this.props.data}} //省略其他邏輯... } class MyComponent3 extends Component { render() { return{this.props.data}} //省略其他邏輯... } const MyComponent2WithPersistentData = withPersistentData(MyComponent2, "data"); const MyComponent3WithPersistentData = withPersistentData(MyComponent3, "name");
新版本的withPersistentData就滿足我們獲取不同key的值的需求了。高階組件中的參數當然也可以是函數,我們將在下一節進一步說明。
3. 進階用法高階組件最常見的函數簽名形式是這樣的:
HOC([param])([WrappedComponent])
用這種形式改寫withPersistentData,如下:
import React, { Component } from "react" const withPersistentData = (key) => (WrappedComponent) => { return class extends Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({data}); } render() { // 通過{...this.props} 把傳遞給當前組件的屬性繼續傳遞給被包裝的組件WrappedComponent return} } } class MyComponent2 extends Component { render() { return {this.props.data}} //省略其他邏輯... } class MyComponent3 extends Component { render() { return{this.props.data}} //省略其他邏輯... } const MyComponent2WithPersistentData = withPersistentData("data")(MyComponent2); const MyComponent3WithPersistentData = withPersistentData("name")(MyComponent3);
實際上,此時的withPersistentData和我們最初對高階組件的定義已經不同。它已經變成了一個高階函數,但這個高階函數的返回值是一個高階組件。HOC([param])([WrappedComponent])這種形式中,HOC([param])才是真正的高階組件,我們可以把它看成高階組件的變種形式。這種形式的高階組件因其特有的便利性——結構清晰(普通參數和被包裹組件分離)、易于組合,大量出現在第三方庫中。如react-redux中的connect就是一個典型。connect的定義如下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(WrappedComponent)
這個函數會將一個React組件連接到Redux 的 store。在連接的過程中,connect通過函數類型的參數mapStateToProps,從全局store中取出當前組件需要的state,并把state轉化成當前組件的props;同時通過函數類型的參數mapDispatchToProps,把當前組件用到的Redux的action creators,以props的方式傳遞給當前組件。
例如,我們把組件ComponentA連接到Redux上的寫法類似于:
const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);
我們可以把它拆分來看:
// connect 是一個函數,返回值enhance也是一個函數 const enhance = connect(mapStateToProps, mapDispatchToProps); // enhance是一個高階組件 const ConnectedComponentA = enhance(ComponentA);
當多個函數的輸出和它的輸入類型相同時,這些函數是很容易組合到一起使用的。例如,有f,g,h三個高階組件,都只接受一個組件作為參數,于是我們可以很方便的嵌套使用它們:f( g( h(WrappedComponent) ) )。這里可以有一個例外,即最內層的高階組件h可以有多個參數,但其他高階組件必須只能接收一個參數,只有這樣才能保證內層的函數返回值和外層的函數參數數量一致(都只有1個)。
例如我們將connect和另一個打印日志的高階組件withLog聯合使用:
const ConnectedComponentA = connect(mapStateToProps)(withLog(ComponentA));
這里我們定義一個工具函數:compose(...functions),調用compose(f, g, h) 等價于 (...args) => f(g(h(...args)))。用compose函數我們可以把高階組件嵌套的寫法打平:
const enhance = compose( connect(mapStateToProps), withLog ); const ConnectedComponentA = enhance(ComponentA);
像Redux等很多第三方庫都提供了compose的實現,compose結合高階組件使用,可以顯著提高代碼的可讀性和邏輯的清晰度。
4.與父組件區別有些同學可能會覺得高階組件有些類似父組件的使用。例如,我們完全可以把高階組件中的邏輯放到一個父組件中去執行,執行完成的結果再傳遞給子組件。從邏輯的執行流程上來看,高階組件確實和父組件比較相像,但是高階組件強調的是邏輯的抽象。高階組件是一個函數,函數關注的是邏輯;父組件是一個組件,組件主要關注的是UI/DOM。如果邏輯是與DOM直接相關的,那么這部分邏輯適合放到父組件中實現;如果邏輯是與DOM不直接相關的,那么這部分邏輯適合使用高階組件抽象,如數據校驗、請求發送等。
5. 注意事項1)不要在組件的render方法中使用高階組件,盡量也不要在組件的其他生命周期方法中使用高階組件。因為高階組件每次都會返回一個新的組件,在render中使用會導致每次渲染出來的組件都不相等(===),于是每次render,組件都會卸載(unmount),然后重新掛載(mount),既影響了效率,又丟失了組件及其子組件的狀態。高階組件最適合使用的地方是在組件定義的外部,這樣就不會受到組件生命周期的影響了。
2)如果需要使用被包裝組件的靜態方法,那么必須手動拷貝這些靜態方法。因為高階組件返回的新組件,是不包含被包裝組件的靜態方法。hoist-non-react-statics可以幫助我們方便的拷貝組件所有的自定義靜態方法。有興趣的同學可以自行了解。
3)Refs不會被傳遞給被包裝組件。盡管在定義高階組件時,我們會把所有的屬性都傳遞給被包裝組件,但是ref并不會傳遞給被包裝組件。如果你在高階組件的返回組件中定義了ref,那么它指向的是這個返回的新組件,而不是內部被包裝的組件。如果你希望獲取被包裝組件的引用,你可以把ref的回調函數定義成一個普通屬性(給它一個ref以外的名字)。下面的例子就用inputRef這個屬性名代替了常規的ref命名:
function FocusInput({ inputRef, ...rest }) { return ; } //enhance 是一個高階組件 const EnhanceInput = enhance(FocusInput); // 在一個組件的render方法中... return (下篇預告:{ this.input = input } }>) // 讓FocusInput自動獲取焦點 this.input.focus();
React 深入系列7:React 常用模式
我的新書《React進階之路》已上市,請大家多多支持!
鏈接:京東 當當
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95196.html
摘要:本篇是深入系列的最后一篇,將介紹開發應用時,經常用到的模式,這些模式并非都有官方名稱,所以有些模式的命名并不一定準確,請讀者主要關注模式的內容。 React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。 本篇是React深入系列的最后一篇,將介紹開發React應用時,經常用到的模式,這些模式并非都有...
摘要:為了代碼進一步解耦,可以考慮使用高階組件這種模式。開源的高階組件使用提供了一系列使用的高階組件,可以增強組件的行為,可以利用此庫學習高階組件的寫法。通過使用此庫提供的高階組件,可以方便地讓列表元素可拖動。 1. Decorator基本知識 在很多框架和庫中看到它的身影,尤其是React和Redux,還有mobx中,那什么是裝飾器呢。 修飾器(Decorator)是一個函數,用來修改類的...
摘要:博客地址背景知識在開始講述高階組件前,我們先來回顧高階函數的定義接收函數作為輸入,或者輸出另一個函數的一類函數,被稱作高階函數。 博客地址:http://www.luckyjing.com/post... 背景知識 在開始講述高階組件前,我們先來回顧高階函數的定義:接收函數作為輸入,或者輸出另一個函數的一類函數,被稱作高階函數。對于高階組件,它描述的便是接受React組件作為輸入,輸出...
閱讀 3952·2021-11-11 10:58
閱讀 3321·2021-09-26 09:46
閱讀 1912·2019-08-30 15:55
閱讀 976·2019-08-30 13:52
閱讀 1943·2019-08-29 13:11
閱讀 3024·2019-08-29 11:27
閱讀 1517·2019-08-26 18:18
閱讀 2618·2019-08-23 14:17