React系列之高階組件HOC實際應用指南

前言

Higher-Order function(高階函數)你們很熟悉,在函數式編程中的一個基本概念,它描述了這樣一種函數:這種函數接受函數做爲輸出,或者輸出一個函數。好比經常使用的工具方法reduce,map等都是高階函數

如今咱們都知道高階函數是什麼,Higher-Ordercomponents(高階組件)其實也是相似於高階函數,它接受一個React組件做爲輸入,輸出一個新的React組件html

Concretely, a higher-order component is a function that takes a component and returns a new component.react

通俗的語言解釋:當咱們用一個容器(w)把React組件包裹,高階組件會返回一個加強(E)的組件。高階組件讓咱們的代碼更具備複用性,邏輯性與抽象特。它能夠對props和state進行控制,也能夠對render方法進行劫持...編程

大概是這樣:redux

const EnhancedComponent = higherOrderComponent(WrappedComponent)api

簡單例子:bash

import React, { Component } from 'react';
import ExampleHoc from './example-hoc';

class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
    <div>
       {this.props.title} - {this.props.name}
    </div>
    )
  }
}
export default ExampleHoc(UseContent)
複製代碼
import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         title: 'hoc-component',
         name: 'arcsin1',
        }
    }
    render() {
       const newProps = {
        ...this.state,
       }
       return <WrappedComponent {...this.props} {...this.newProps} />
    }
  }
}
export default ExampleHoc
複製代碼

組件UseContent,你能夠看到實際上是一個很簡單的一個渲染而已,而組件ExampleHoc對它進行了加強,很簡單的功能.app

應用場景

如下代碼我會用裝飾器(decorator)書寫函數式編程

屬性代理。 高階組件經過被包裹的React組件來操做props

反向繼承。 高階組件繼承於被包裹的React組件

1. 屬性代理

小列子說明:函數

import React, { Component } from 'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
        <div>
           {...this.props} //這裏只是演示
        </div>
    )
  }
}
複製代碼
import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return <WrappedComponent {...this.props} />
    }
  }
}
export default ExampleHoc
複製代碼

這樣的組件就能夠做爲參數被調用,原始組件就具有了高階組件對它的修飾。就這麼簡單,保持單個組件封裝性的同時還保留了易用性。固然上述的生命週期以下:工具

didmount -> HOC didmount ->(HOCs didmount) ->(HOCs willunmount)-> HOC willunmount -> unmount

  • 控制props

    我能夠讀取,編輯,增長,移除從WrappedComponent傳來的props,可是須要當心編輯和移除props。咱們應該對高階組件的props做新的命名防止混淆了。

    例如:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       const newProps = {
           name: newText,
       }
       return <WrappedComponent {...this.props}  {...newProps}/>
    }
  }
}
export default ExampleHoc
複製代碼
  • 經過refs使用引用

    在高階組件中,咱們能夠接受refs使用WrappedComponent的引用。 例如:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    proc = wrappedComponentInstance => {
        wrappedComponentInstance.method()
    }
    render() {
       const newProps = Object.assign({}, this.props,{
           ref: this.proc,
       })
       return <WrappedComponent {...this.newProps} />
    }
  }
}
export default ExampleHoc
複製代碼

當WrappedComponent被渲染的時候,refs回調函數就會被執行,這樣就會拿到一份WrappedComponent的實例的引用。這樣就能夠方便地用於讀取和增長實例props,並調用實例。

  • 抽象state

咱們能夠經過WrappedComponent提供props和回調函數抽象state。就像咱們開始的例子,咱們能夠把原組件抽象爲展現型組件,分離內部狀態,搞成無狀態組件。

例子:

import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         name: '',
        }
    }
    onNameChange = e => {
        this.setState({
            name: e.target.value,
        })
    }
    render() {
       const newProps = {
         name: {
            value: this.state.name,
            onChange: this.onNameChange,
         }
       }
       return <WrappedComponent {...this.props} {...newProps} />
    }
  }
}
export default ExampleHoc
複製代碼

在上面 咱們經過把input的name prop和onchange方法提到了高階組件中,這樣有效的抽象了一樣的state操做。

用法:
import React, { Component } from 'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
        <input name="name" {this.props.name} />
    )
  }
}
這樣就是一個受控組件
複製代碼
  • 其它元素包裹WrappedComponent

    其它,咱們可使用其它元素包裹WrappedComponent,這樣既能夠增長樣式,也能夠方便佈局。例如

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return (
            <div style={{display: 'flex'}}>
                <WrappedComponent {...this.props} />
            </div>
       )
    }
  }
}
export default ExampleHoc
複製代碼

2. 反向繼承

從字面意思,能夠看出它與繼承相關,先看看例子:

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

正如看見的,高階組件返回的組件繼承與WrappedComponent,由於被動繼承了WrappedComponent,全部的調用都是反向。因此這就是反代繼承的由來。 這種方法與屬性代理不太同樣,它經過繼承WrappedComponent來實現,方法能夠經過super來順序調用,來看看生命週期:

didmount -> HOC didmount ->(HOCs didmount) -> willunmount -> HOC willunmount ->(HOCs willunmount)

在反向繼承中,高階函數可使用WrappedComponent的引用,這意味着可使用WrappedComponent的state,props,生命週期和render方法。但它並不能保證完整的子組件樹被解析,得注意。

  • 渲染劫持

渲染劫持就是高階組件能夠控制WrappedComponent的渲染過程,並渲染各類各樣的結果。咱們能夠在這個過程當中任何React元素的結果中讀取,增長,修改,刪除props,或者修改React的元素樹,又或者用樣式控制包裹這個React元素樹。

由於前面說了它並不能保證完整的子組件樹被解析,有個說法:咱們能夠操控WrappedComponent元素樹,並輸出正確結果,但若是元素樹種包含了函數類型的React組件,就不能操做組件的子組件。

先看看有條件的渲染例子:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      if(this.props.loggedIn) { //當登陸了就渲染
           return super.render()
      } else {
          return null
      }
      
    }
  }
}
複製代碼

對render輸出結果的修改:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      const eleTree = super.render()
      let newProps = {}
      
      if(eleTree && eleTree.type === 'input') { 
           newProps = {value: '這不能被渲染'}
      } 
      const props = Object.assgin({},eleTree.props,newProps)
      const newEleTree = React.cloneElement(eleTree, props, eleTree.props.children)
      return newEleTree
      
    }
  }
}
複製代碼
  • 控制state

    高階組件是能夠讀取,修改,刪除WrappedComponent實例的state,若是須要的話,也能夠增長state,但這樣你的WrappedComponent會變得一團糟。所以大部分的高階組件多都應該限制讀取或者增長state,尤爲是增長state,能夠經過從新命名state,以防止混淆。

    看看例子:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      <div>
       <h3>HOC debugger</h3>
       <p>Props <pre>{JSON.stringfy(this.props,null,1)}</pre></p>
       <p>State <pre>{JSON.stringfy(this.state,null,2)}</pre></p>
       {super.render()}
      </div>
    }
  }
}
複製代碼

高階組件接受其它參數

舉個列子,我把用戶信息存在本地LocalStorage中,固然裏面有不少key,可是我不須要用到全部,我但願按照個人喜愛獲得我本身想要的。

import React, { Component } from 'react'

const ExampleHoc = (key) => (WrappedComponent) => {

  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

複製代碼
import React, { Component } from 'react'

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

}

const MyComponent2WithHOC = ExampleHoc(MyComponent2, 'data')

export default MyComponent2WithHOC

複製代碼
import React, { Component } from 'react'

class MyComponent3 extends Component {  
  render() {
    return <div>{this.props.data}</div>
  }
}
const MyComponent3WithHOC = ExampleHoc(MyComponent3, 'name')

export default MyComponent3WithHOC
複製代碼

實際上,此時的ExampleHoc和咱們最初對高階組件的定義已經不一樣。它已經變成了一個高階函數,但這個高階函數的返回值是一個高階組件。咱們能夠把它當作高階組件的變種形式。這種形式的高階組件大量出如今第三方庫中。如react-redux中的connect就是一個典型。請去查看react-redux的api就能夠知道了。

有問題望指出,謝謝!

參考:

  1. Higher-Order Components: higher-order-components

  2. React Higher Order Components in depth: React Higher Order Components in depth

相關文章
相關標籤/搜索