React高階組件總結

在多個不一樣的組件中須要用到相同的功能,這個解決方法,一般有Mixin和高階組件。react

Mixin方法例如:es6

//給全部組件添加一個name屬性
var defaultMixin = {
    getDefaultProps: function() {
        return {
            name: "Allen"
        }
    }
}

var Component = React.createClass({
    mixins: [defaultMixin],
    render: function() {
        return <h1>Hello, {this.props.name}</h1>
    }
})

可是因爲Mixin過多會使得組件難以維護,在React ES6中Mixin再也不被支持。redux

高階組件是一個接替Mixin實現抽象組件公共功能的好方法。高階組件實際上是一個函數,接收一個組件做爲參數,
返回一個包裝組件做爲返回值,相似於高階函數。高階組件和裝飾器就是一個模式,所以,高階組件能夠做爲
裝飾器來使用。高階組件有以下好處:segmentfault

1. 適用範圍廣,它不須要es6或者其它須要編譯的特性,有函數的地方,就有HOC。
2. Debug友好,它可以被React組件樹顯示,因此能夠很清楚地知道有多少層,每層作了什麼。網絡

高階組件基本形式:const EnhancedComponent = higherOrderComponent(WrappedComponent);app

詳細以下:async

function hoc(ComponentClass) {
    return class HOC extends React.Component {
        componentDidMount() {
            console.log("hoc");
        }

        render() {
            return <ComponentClass />
        }
    }
}
//使用高階組件
class ComponentClass extends React.Component {
    render() {
        return <div></div>
    }
}

export default hoc(MyComponent);

//做爲裝飾器使用
@hoc
export default class ComponentClass extends React.Component {
    //...
}

高階組件有兩種常見的用法:函數

1. 屬性代理(Props Proxy): 高階組件經過ComponentClass的props來進行相關操做
2. 繼承反轉(Inheritance Inversion)): 高階組件繼承自ComponentClass佈局

1. 屬性代理(Props Proxy)性能

屬性代理有以下4點常見做用:

1. 操做props
2. 經過refs訪問組件實例
3. 提取state
4. 用其餘元素包裹WrappedComponent,實現佈局等目的

(1). 操做props

能夠對原組件的props進行增刪改查,一般是查找和增長,刪除和修改的話,須要考慮到不能破壞原組件。
下面是添加新的props:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

(2). 經過refs訪問組件實例

能夠經過ref回調函數的形式來訪問傳入組件的實例,進而調用組件相關方法或其餘操做。
例如:

//WrappedComponent初始渲染時候會調用ref回調,傳入組件實例,在proc方法中,就能夠調用組件方法
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }

    render() {
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}

(3). 提取state

你能夠經過傳入 props 和回調函數把 state 提取出來,相似於 smart component 與 dumb component。更多關於 dumb and smart component。
提取 state 的例子:提取了 input 的 value 和 onChange 方法。這個簡單的例子不是很常規,但足夠說明問題。

function ppHOC(WrappedComponent) {
  return class PP extends React.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}/>
    }
  }
}

//使用方式以下
@ppHOC
class Example extends React.Component {
  render() {
    //使用ppHOC裝飾器以後,組件的props被添加了name屬性,能夠經過下面的方法,將value和onChange添加到input上面
    //input會成爲受控組件
    return <input name="name" {...this.props.name}/>
  }
}

(4). 包裹WrappedComponent

爲了封裝樣式、佈局等目的,能夠將WrappedComponent用組件或元素包裹起來。
例如:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return (
        <div style={{display: 'block'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

2. 繼承反轉(Inheritance Inversion)

HOC繼承了WrappedComponent,意味着能夠訪問到WrappedComponent的state,props,生命週期和render方法。若是在HOC中定義了與WrappedComponent同名方法,將會發生覆蓋,就必須手動經過super進行調用。經過徹底操做WrappedComponent的render方法返回的元素樹,能夠真正實現渲染劫持。這種思想具備較強的入侵性。

大體形式以下:

function ppHOC(WrappedComponent) {
  return class ExampleEnhance extends WrappedComponent {
    ...
    componentDidMount() {
      super.componentDidMount();
    }
    componentWillUnmount() {
      super.componentWillUnmount();
    }
    render() {
      ...
      return super.render();
    }
  }
}

例如,實現一個顯示loading的請求。組件中存在網絡請求,完成請求前顯示loading,完成後再顯示具體內容。
能夠用高階組件實現以下:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            if (this.state.success) {
                return super.render()
            }
            return <div>Loading...</div>
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    state = {
        success: false,
        data: null
    };
    async componentDidMount() {
        const result = await fetch(...請求);          
     this.setState({ success: true, data: result.data }); } render() { return <div>主要內容</div> } }

(1) 渲染劫持

繼承反轉這種模式,能夠劫持被繼承class的render內容,進行修改,過濾後,返回新的顯示內容。
之因此被稱爲渲染劫持是由於 HOC 控制着 WrappedComponent 的渲染輸出,能夠用它作各類各樣的事。

經過渲染劫持,你能夠完成:

在由 render輸出的任何 React 元素中讀取、添加、編輯、刪除 props
讀取和修改由 render 輸出的 React 元素樹
有條件地渲染元素樹
把樣式包裹進元素樹,就行Props Proxy那樣包裹其餘的元素

注:在 Props Proxy 中不能作到渲染劫持。
雖然經過 WrappedComponent.prototype.render 你能夠訪問到 render 方法,不過還須要模擬 WrappedComponent 的實例和它的 props,還可能親自處理組件的生命週期,而不是交給 React。記住,React 在內部處理了組件實例,你處理實例的惟一方法是經過 this 或者 refs。

例以下面,過濾掉原組件中的ul元素:

function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            const elementTree = super.render();
            elementTree.props.children = elementTree.props.children.filter((z) => {
                return z.type !== "ul" && z;
            }
            const newTree = React.cloneElement(elementTree);
            return newTree;
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    render() {
        const divStyle = {
            width: '100px',
            height: '100px',
            backgroundColor: 'red'
        };

        return (
            <div>
                <p style={{color: 'brown'}}>啦啦啦</p>
                <ul>
                    <li>1</li>
                    <li>2</li>
                </ul>
                <h1>哈哈哈</h1>
            </div>
        )
    }
}

(2) 操做state

HOC能夠讀取,編輯和刪除WrappedComponent實例的state,能夠添加state。不過這個可能會破壞WrappedComponent的state,因此,要限制HOC讀取或添加state,添加的state應該放在單獨的命名空間裏,而不是和WrappedComponent的state混在一塊兒。
例如:經過訪問WrappedComponent的props和state來作調試

export function IIHOCDEBUGGER(WrappedComponent) {
  return class II extends WrappedComponent {
    render() {
      return (
        <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>
          {super.render()}
        </div>
      )
    }
  }
}

(3) 條件渲染

當 this.props.loggedIn 爲 true 時,這個 HOC 會徹底渲染 WrappedComponent 的渲染結果。(假設 HOC 接收到了 loggedIn 這個 prop)

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}

(4) 解決WrappedComponent名字丟失問題

用HOC包裹的組件會丟失原先的名字,影響開發和調試。能夠經過在WrappedComponent的名字上加一些前綴來做爲HOC的名字,以方便調試。
例如:

//
class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
  ...
}

//getDisplayName
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName ||
         WrappedComponent.name ||
         ‘Component’
}

(5) 實際應用

1. mobx-react就是高階組件是一個實際應用

@observer裝飾器將組件包裝爲高階組件,傳入組件MyComponent後,mobx-react會對其生命週期進行各類處理,並經過調用forceUpdate來進行刷新實現最小粒度的渲染。mobx提倡一份數據引用,而redux中則提倡immutable思想,每次返回新對象。

2. 實現一個從localStorage返回記錄的功能

//經過多重高階組件肯定key並設定組件
const withStorage = (key) => (WrappedComponent) => {
  return class extends Component {
    componentWillMount() {
        let data = localStorage.getItem(key);
        this.setState({data});
    }
    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

@withStorage('data')
class MyComponent2 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}

@withStorage('name')
class MyComponent3 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}

3. 實現打點計時功能

(1). Props Proxy方式

//性能追蹤:渲染時間打點
export default (Target) => (props)=>{
    let func1 = Target.prototype['componentWillMount']    
    let func2 = Target.prototype['componentDidMount']//Demo並無在prototype上定義該方法,func2爲undefined,可是並不會有影響,這樣作只是爲了事先提取出可能定義的邏輯,保持原函數的純淨
    let begin, end;
    Target.prototype['componentWillMount'] = function (...argus){//do not use arrow funciton to bind 'this' object
        func1.apply(this,argus);//執行原有的邏輯
        begin = Date.now();
    }
    Target.prototype['componentDidMount'] = function (...argus){
        func2.apply(this,argus);//執行原有的邏輯
        end = Date.now();
        console.log(Target.name+'組件渲染時間:'+(end-begin)+'毫秒')
    }
    return <Target {...props}/>//do not forget to pass props to the element of Target
}

(2) Inheritance Inversion方式

// 另外一種HOC的實現方式 Inheritance Inversion
export default Target => class Enhancer extends Target {
    constructor(p){
        super(p);//es6 繼承父類的this對象,並對其修改,因此this上的屬性也被繼承過來,能夠訪問,如state
        this.end =0;
        this.begin=0;
    }
    componentWillMount(){
        super.componentWilMount && super.componentWilMount();// 若是父類沒有定義該方法,直接調用會出錯
        this.begin = Date.now();
    }
    componentDidMount(){
        super.componentDidMount && super.componentDidMount();
        this.end=Date.now();
        console.log(Target.name+'組件渲染時間'+(this.end-this.begin)+'ms')
    }
    render(){
        let ele = super.render();//調用父類的render方法
        return ele;//能夠在這以前完成渲染劫持
    }
}

 

 

參考:https://zhuanlan.zhihu.com/p/24776678?group_id=802649040843051008    https://blog.csdn.net/cqm1994617/article/details/54800360      https://blog.csdn.net/xiangzhihong8/article/details/73459720    https://segmentfault.com/a/1190000004598113    https://blog.csdn.net/sinat_17775997/article/details/79087977    https://blog.csdn.net/neoveee/article/details/69212146

相關文章
相關標籤/搜索