【ReactNative】高階組件

理解 React 高階組件

高階組件相似於高階函數,如 map()reduce()sort(),即這樣一種函數:接收函數做爲輸入,或是輸出一個函數。一樣做爲高階組件:它接受 React 組件做爲輸入,輸出一個新的 React 組件。前端

比較典型的高階組件是 react-redux 中的 connnect() 函數,通常的使用狀況: export default connect(mapStateToProps, mapDispatchToProps)(WrappedComponent)。首先,connect(mapStateToProps, mapDispatchToProps) 執行後返回高階組件函數,而後,輸入(WrappedComponent) 組件返回處理事後的組件並導出。react

先看一個很是簡單的高階組件的例子:redux

const HocComponent = (WarppedComponent) => {
    return class WrappingComponent extends Component {
        render() {
            return <WarppedComponent/>;
        }
    };
};

export default HocComponent;
複製代碼

經過示例可知 高階組件 實際上是一個函數:輸入一個組件,輸出一個新的組件,這個新的組件是對輸入組件的一個加強。其實 高階組件 並非真正的組件,比較嚴謹的說法是: 高階組件工廠函數。但在業界,更遵照廣泛的定義:即 具備加強組件功能的函數 稱爲 高階組件數組

那麼定義高階組件的意義在哪?promise

首先,重用代碼。如比較常見的狀況:不少組件都須要一個公共的邏輯,那麼這個邏輯,沒有必要在每一個組件當中都實現一遍。最好的方式是將這部分邏輯抽取出來,利用 高階組件 的方式拋出,這樣就會減小不少組件當中的重複代碼。網絡

其次,修改組件行爲。好比有的時候,想對一個組件作一些操做。可是又不想觸碰其中的內部邏輯,這時能夠經過一個函數生成另外一個組件幷包裹原組件,組件之間相對獨立,又不會侵入原組件。app

屬性代理高階組件

這是實現高階組件比較經常使用的一種模式, 文章開頭的代碼示例就是一個代理方式的高階組件,只不過沒有實現任何功能。新生成的組件 繼承自 React.Component,同時 新生成的組件傳入組件代理, 並將 傳入的組件 顯示出來。新生成的組件 本身會作一些事情,其他工做所有由 傳入的組件 實現。固然,所謂的 新生成組件 就是 高階組件 了。代理方式的高階組件 主要有下面幾種應用場景:函數

  • 操控Props

    能夠 編輯 傳入的組件props性能

    const addHOCComponent = (WrappedComponent) => {
        return class WrappingComponent extends Component {
             render() {
                 const newProps = {add: 'newProps'};
                 return <WrappedComponent {...this.props} {...newProps}/>; } } } export default AddHOCComponent; 複製代碼

    當使用 高階組件函數 時, 函數返回的 新的組件 會多了一個 addprop。 下面是移除一個 prop 的例子:優化

    const removeHOCComponent = (WrappedComponent) => {
          return class WrappingComponent extends Component {
             render() {
                   const {remove, ...otherProps} = this.props;
                   return <WrappedComponent {...otherProps}/>; } } } export default RemoveHOCComponent; 複製代碼

    RemoveHOCComponent 組件和 WrappedComponent 具備徹底一致的行爲,除了它們的 props 不同。

    如上代碼可知,高階組件的複用性是很強的,經過高階組件能夠垂手可得的操控 props ,使用高階組件代碼以下:

    const AddPropHoc = addHOCComponent(SomeComponent);
    const RemovePropHoc = removeHOCComponent(OtherComponent);
    複製代碼
  • 抽取 State

    高階組件 能夠將 原組件內部狀態 分離,使其僅僅具備 展現組件 的功能。

    const ExtractStateHOC = (WrappedComponent) => {
        return class WrappingComponent extends Component {
            constructor(props) {
                 super(props);
                 this.state = {
                  name: 'ExtractState'
                 };
                this.onChange = this
                    .onChange
                    .bind(this);
             }
            render() {
                 const newProps = {
                    name: this.state.name,
                    onChange: this.onChange
                 };
                 return (<WrappedComponent {...newProps}/>) } onChange(event) { this.setState({name: event.nativeEvent.text}) } } }; export default ExtractStateHOC; 複製代碼

    ExtractStateHOC 函數將 WrappedComponentprops 增長了 nameonChange 。而後經過 props 傳遞給原組件,這樣原組件的 內部狀態 就被抽離到了 高階組件 中去處理。下面是個具體例子:

    class TextInputComponent extends Component {
        constructor(props) {
             super(props);
             this.state = {};
        }
    
         render() {
             const {onChange, name} = this.props;
             return (
                   <View style={styles.container}> <Text>{name}</Text> <TextInput onChange={onChange}></TextInput> </View>
             );
        }    
    }
    
    const ExtractStateComponent = ExtractStateHOC(TextInputComponent);
    複製代碼
  • 包裝組件

    上文中寫到的經過 高階組件 產生的 新組件 都是經過render() 函數直接返回 原組件, 其實也能夠在 原組件 的基礎之上添加一些其餘的組件。

    const PackagingHOC = (WrappedComponent, style) => {
    return class WrappingComponent extends Component {
        render() {
            return (
                <View style={style}> <WrappedComponent/> </View>
                 )
             }
         }
    };
    
    export default PackagingHOC;
    複製代碼

反向繼承高階組件

繼承方式的高階組件採用 繼承 的方式將 原組件新生成的組件 關聯起來。即 新組件 繼承自 原組件 的方式。

const ExtendsHOC = (WrappedComponent) => {
    return class WrappingComponent extends WrappedComponent {
        render() {
           return super.render();
        }
    }
}
複製代碼

由於是 繼承 的關係,因此在 render() 函數中直接經過 super.render() 就能夠渲染出父類的元素。代理方式的高階組件是生成新的組件,原組件和新組件是兩個不一樣的組件,每次渲染,兩個組件都要經歷完整的生命週期。而到了 繼承方式的高階組件 ,兩個組件的生命週期合併在了一塊兒,有相同的生命週期。

  • 操控Props

    該方式一樣能夠操控 Props,使用 React.cloneElement 從新繪製從父類獲得的元素。

    const ExtendsHOC = (WrappedComponent) => {
         return class WrappingComponent extends WrappedComponent {
             render() {
                 const elements = super.render();
                 const newProps = {
                      name: elements.type.displayName === 'Text' ? 'extends': 'other',
                     ...this.props
                 };
                 return React.cloneElement(elements, newProps, elements.props.children);
             }
        }
    }
    複製代碼

    若是 原組件 的第一層元素是 Text,那麼添加 name。 雖然能夠達到操控 Props 的目的,但這種方式太複雜,沒什麼實際意義,因此不多采用這種方式。除非要根據父類的顯示狀況操控不一樣的 Props

  • 操控生命週期函數

    由於是組件之間是 繼承關係 ,因此能夠操控生命週期,代理方式的高階組件沒法作到這一點,由於它生成的新的組件和原組件沒有實質的聯繫。

    假設有這樣一種需求:某些特定的頁面在要刷新的時,判斷是否有權限,如無則留在原地,不作刷新。

    const ExtendsHOC = (WrappedComponent) => {
         return class WrappingComponent extends WrappedComponent {
             shouldComponentUpdate(nextProps, nextState, nextContext) {
                 return condition;
             }
             render() {
                 return super.render();
              }
         }
    }
    複製代碼

    shouldComponentUpdate() 函數決定是否從新渲染 原組件 的元素。如返回 false 則不會執行 render()

組合式組件

在前端組件式業務開發中,複用是無疑是提升開發效率的一大利器。React 組件經過配置 Props 能夠實現不一樣的業務邏輯,但隨着需求的不斷增多,單純的經過增長 Props 必然會形成 Props 氾濫,給持續維護形成很大障礙。這時候能夠組合式的組件開發方式,將組件可複用的部分抽象爲粒度更小的組件,而後組合在一塊兒獲得完整功能的組件。下面是一個簡單的例子,一個組合式的列表:

  • 點擊事件抽取

    const HandleDecorator = (WrappedComponent) => {
    
      return class WrappingComponent extends Component {   
        handlePress(msg) {
            console.log(msg);
            console.log(this.state);
        }
        
        render() {
            return <WrappedComponent {...this.props} handlePress={this .handlePress .bind(this)}/> } } }; 複製代碼
  • 網絡數據抽取

    const DataDecorator = (WrappedComponent) => {
     return class WrappingComponent extends Component {
        constructor(props) {
            super(props);
    
            this.state = {
                dataSource: []
            };
        }
        componentDidMount() {
    
            const promise = new Promise((resolve) => {
                setTimeout(() => {
                    const simulateData = ['Swift', 'React', 'SwiftUI', 'JavaScript'];
                    resolve(simulateData);
                }, 3000);
            });
    
            promise.then(data => {
                this.setState({
                    dataSource: data
                });
            })            
        }
    
        render() {
            return <WrappedComponent {...this.props} dataSource={this.state.dataSource}/> } } } 複製代碼
  • compose 組合

    compose() 函數提供了將不一樣的函數組合在一塊兒的功能。可將不定數量的函數做爲 compose() 的參數,而後將這些做爲參數的函數按照從右到左的順序執行:第一個被執行的函數的返回值做爲下一個函數的參數,以此類推。

    const ComposeComponent = compose(HandleDecorator, DataDecorator)(ListComponent);
    複製代碼
  • 基礎組件

    到目前爲止,ListComponent 組件只須要讀取被組合增強過的 props 就能夠了。它的數據來源和事件的處理都交給了其餘高階組件處理。這樣,經過簡單的配置工做,就能完成對組件的自由支配,經過更細粒度的高階組件使組件更靈活、更容易擴展。

    class ListComponent extends Component {
    
      render() {
           const {dataSource} = this.props;
             return (<FlatList
                style={styles.container}
                data={dataSource}
                renderItem={this._createRow}
                keyExtractor={this._keyExtractor}
                />);
       };
    
      _createRow = (wrappedItem) => <Button title={wrappedItem.item} onPress={() => this.props.handlePress(wrappedItem.item)}/>
    
     _keyExtractor = (item, index) => item;
    }
    複製代碼

以函數做爲子組件

高階組件對原組件的擴展主要依賴於對 props 的操做,一旦原組件沒有聲明自身能支持的 props ,高階組件對原組件再多的擴展也是無用的。以代理方式的高階組件爲例,高階組件和原組件是父子關係,它們之間天然要經過 props 交互,被加強事後的 props 是否有效,取決於原組件的支持。這樣就產生了必定的侷限性,將子組件當作函數處理能夠解決這個問題。

以這種方式實現代碼重用不是經過函數了,而是經過一個真正的 React 組件,這個組件要求必須有子組件,並且子組件必須是函數。this.props.children 引用的是子組件,獲得的結果在 render() 方法中返回,這樣就將子組件渲染出來了。

以一個倒計時程序爲例:倒計時邏輯是可重用的,倒計時結束後所要顯示的畫面應該是靈活的,因此倒計時器抽取到一個父組件中,並將一些信息經過 props 傳遞給子組件。

class CountDownView extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 10
        };
    }

    componentDidMount() {
        this.timer = setInterval(() => {
            const newCount = this.state.count - 1;
            this.setState({count: newCount});
            console.log(newCount);
        }, 1000);

    }

    render() {
        return this
            .props
            .children(this.state.count);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }
}
複製代碼

上面的代碼設置了一個從 10 開始的倒計時器,每次定時器觸發都會更新 state ,也就每次都會執行 render() ,在其中經過 this.props.childre(this.state.count) 將當前的倒計時數傳遞給子組件。固然,起始 count 也能夠經過 props 傳遞給倒計時器組件,在 count<0 時將定時器移除。

CountDown 封裝好後,就能夠將其應用在所須要倒計時的應用場景,所須要作的僅僅是編寫一個合適的子組件,固然,要是一個函數:

<DetailView >
    {
        (count) => <View style={styles.container}>
            <Text
                style={{
                color: 'black',
                fontSize: 50
            }}>{count > 0
                    ? count
                    : '倒計時結束'}</Text>
             </View>
    }
</DetailView>
複製代碼

由上可知,函數做爲子組件 的模式相對來講靈活性顯然更高。但以 函數做爲子組件 雖然更靈活,但很難作性能上的優化。每次父組件的更新過程都要得到一個函數來渲染子組件,這樣沒法經過 shouldComponentUpdate() 避免沒必要要的渲染。凡事有利即有弊。具體要使用哪一種方式要根據使用場景而定。

就這些了

相關文章
相關標籤/搜索