高階元件

多芒小丸子發表於2018-09-15

高階元件類似於高階函式,它接受React元件作為輸入,輸出一個新的React元件。當React元件被包裹時,高階元件會返回一個增強的React元件,高階元件可以提升程式碼的複用性、邏輯性、抽象性。

實現高階元件有兩種方式。

  • 屬性代理
  • 反向繼承 本篇文章主要分析屬性代理的幾種實現方式。

1. 屬性代理

通過高階元件來傳遞props,這種方法就是屬性代理。下面對屬性代理的幾個功能進行講解。

  • 控制props

我們可以讀取、增加、編輯或是移除從WrappedComponent傳進來的props但需要小心刪除與編輯重要的props,應該儘可能對高階元件的props做新的命名以防止混淆。

例如增加一個新的prop:

import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>  
  class extends Component {
    render () {
      const newProps = {
        text: 'newText'
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
export default Mycontainer
複製程式碼

當呼叫高階元件事,可以用text這個新的props了。對於原元件來說,只要套用這個高階元件,新元件就會多一個屬性。

import React, { Component } from 'react'
import Mycontainer from 'container/Mycontainer'
class Page1 extends Component {
  render() {
    console.log("this.props)
    return <div>測試新新增一個屬性</div>
  }
}
export default Mycontainer(Page1)

複製程式碼
  • 通過refs使用引用

在高階元件中,我們可以接受refs使用WrappedComponent的引用。例如:

import React, { Component } from 'react';

const Mycontainer = (WrappedComponent) =>  
  class extends Component {
    proc(wrappedComponentInstance) {
      console.log("ref回撥函式被執行", wrappedComponentInstance)
      console.log(wrappedComponentInstance.props);
      wrappedComponentInstance.newText = "通過refs新增屬性"
    }
    render () {
      const props = Object.assign({}, this.props, {
        ref: this.proc.bind(this),
      });
      return <WrappedComponent {...props} />
    }
  }
export default Mycontainer
複製程式碼
  • 抽象state
import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>  
  class extends Component {
    constructor(props) {
      super(props);
      this.state = {
        name: '',
      }
    }

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

    render () {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange,
        }
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
export default Mycontainer
複製程式碼
  • 使用其他元素包裹WrappedComponent,這既可以為了增加樣式,也可以是為了佈局。
import React, { Component } from 'react';

const Mycontainer = (WrappedComponent) =>  
  class extends Component {
    render () {
      const newProps = {
        text: 'newText'
      }
      return (
        <div style={{display:"block"}}>
          <WrappedComponent {...this.prop} />
        </div>
      )
   }
  }
export default Mycontainer
複製程式碼

2. 反向繼承

另一種高階元件的實現方式成為反向繼承,一個簡單的實現

const MyContainer = (WrappedComponent) => {
    class extends WrappedComponent {
        render() {
            return super.render();
        }
    }
}
複製程式碼

正如所見,高階元件返回的元件繼承與WrappedComponent。因為被動的繼承了WrappedComponent,所有的呼叫都會反向。 這種方法與屬性代理不太一樣。他通過繼承WrappedComponent來實現,方法可以通過super來順序呼叫,反向繼承不能保證完整的子樹被解析。 反向繼承有兩個比較大的特點。

  • 渲染劫持

渲染劫持指的是高階元件可以控制WrappedComponent的渲染過程,並渲染各種各樣的結果。我們可以在這個過程中在任何React元素輸出的結果中讀取、增加、修改、刪除props,或讀取或修改React樹,或條件顯示樹,又或是用樣式控制包裹樹。

  1. 一個條件渲染的示例:
const MyContainer = (WrappedComponent) => {
    class extends WrappedComponent {
        render() {
            if(this.props.loggedIn) {
                return super.render();
            } else {
                return null;
            }
        }
    }
}
複製程式碼
  1. 我們可以對render的輸出結果進行修改
const MyContainer = (WrappedComponent) => {
    class extends WrappedComponent {
        render() {
            const elementsTree = super.render();
            let newProps = {};
            if(elementsTree && elementsTree.type === 'input') {
                newProps = {
                    value:'may the force be with you'
                };
            }
            const props = Object.assign({},elementsTree.props,newProps);
            const newElementsTree = React.cloneElement(elementsTree,props,elementsTree.props.children);
            return newElementsTree;
        }
    }
}
複製程式碼
  • 控制state

高階元件可以讀取、修改或刪除WrappedComponent例項中的state,如果需要的話,也可以增加state.但這樣做,可能會讓WrappedComponent元件內部變得一團糟。大部分高階元件都應該限制讀取或增加state,尤其是後者,可以通過重新命名state,以防止混淆。

const MyContainer = (WrappedComponent) => {
    class extends WrappedComponent {
        render() {
            <div>
                <h2>HOC Debugger Component</h2>
                <p>props</p><pre>{JSON.stringify(this.props,null,2)}</pre>
                <p>state</p><pre>{JSON.stringify(this.state,null,2)}</pre>
            </div>
        }
    }
}
複製程式碼

這個例子中,顯示了WrappedComponent的props和state,以方便我們在程式中區除錯他們。

3.元件命名

4.元件引數

有時,我們呼叫高階元件時需要傳入一些引數。

function HOCFactoryFactory(...params) {
    return function HOCFactory(WrappedComponent) {
        return class HOC extends Component {
            render() {
                return <WrappedComponent {...this.props}
            }
        }
    }
}
複製程式碼

相關文章